View Javadoc

1   /*
2    * Copyright 2006-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.kuali.rice.kew.rule.service.impl;
18  
19  import java.io.InputStream;
20  import java.sql.Timestamp;
21  import java.text.ParseException;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.UUID;
33  
34  import javax.xml.namespace.QName;
35  
36  import org.apache.commons.beanutils.PropertyUtils;
37  import org.apache.commons.lang.ObjectUtils;
38  import org.apache.commons.lang.StringUtils;
39  import org.jdom.Element;
40  import org.kuali.rice.core.api.impex.ExportDataSet;
41  import org.kuali.rice.core.api.impex.xml.XmlConstants;
42  import org.kuali.rice.core.framework.services.CoreFrameworkServiceLocator;
43  import org.kuali.rice.core.util.CollectionUtils;
44  import org.kuali.rice.core.util.RiceConstants;
45  import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
46  import org.kuali.rice.kew.doctype.bo.DocumentType;
47  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
48  import org.kuali.rice.kew.exception.WorkflowRuntimeException;
49  import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
50  import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl;
51  import org.kuali.rice.kew.messaging.MessageServiceNames;
52  import org.kuali.rice.kew.responsibility.service.ResponsibilityIdService;
53  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
54  import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
55  import org.kuali.rice.kew.rule.RuleBaseValues;
56  import org.kuali.rice.kew.rule.RuleDelegation;
57  import org.kuali.rice.kew.rule.RuleExtension;
58  import org.kuali.rice.kew.rule.RuleExtensionValue;
59  import org.kuali.rice.kew.rule.RuleResponsibility;
60  import org.kuali.rice.kew.rule.RuleRoutingDefinition;
61  import org.kuali.rice.kew.rule.RuleValidationAttribute;
62  import org.kuali.rice.kew.rule.bo.RuleTemplate;
63  import org.kuali.rice.kew.rule.bo.RuleTemplateAttribute;
64  import org.kuali.rice.kew.rule.dao.RuleDAO;
65  import org.kuali.rice.kew.rule.dao.RuleResponsibilityDAO;
66  import org.kuali.rice.kew.rule.service.RuleCacheProcessor;
67  import org.kuali.rice.kew.rule.service.RuleDelegationCacheProcessor;
68  import org.kuali.rice.kew.rule.service.RuleDelegationService;
69  import org.kuali.rice.kew.rule.service.RuleService;
70  import org.kuali.rice.kew.rule.service.RuleTemplateService;
71  import org.kuali.rice.kew.service.KEWServiceLocator;
72  import org.kuali.rice.kew.service.WorkflowDocument;
73  import org.kuali.rice.kew.util.KEWConstants;
74  import org.kuali.rice.kew.util.PerformanceLogger;
75  import org.kuali.rice.kew.validation.RuleValidationContext;
76  import org.kuali.rice.kew.validation.ValidationResults;
77  import org.kuali.rice.kew.xml.RuleXmlParser;
78  import org.kuali.rice.kew.xml.export.RuleXmlExporter;
79  import org.kuali.rice.kim.api.entity.principal.PrincipalContract;
80  import org.kuali.rice.kim.api.group.Group;
81  import org.kuali.rice.kim.api.services.IdentityManagementService;
82  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
83  
84  import org.kuali.rice.kns.UserSession;
85  import org.kuali.rice.kns.util.GlobalVariables;
86  import org.kuali.rice.kns.util.KNSConstants;
87  import org.kuali.rice.ksb.api.KsbApiServiceLocator;
88  
89  
90  public class RuleServiceImpl implements RuleService {
91  
92      private static final String USING_RULE_CACHE_IND = "CACHING_IND";
93      private static final String XML_PARSE_ERROR = "general.error.parsexml";
94      private static final String RULE_GROUP_CACHE = "org.kuali.workflow.rules.RuleCache";
95  
96      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RuleServiceImpl.class);
97  
98      private RuleDAO ruleDAO;
99      private RuleResponsibilityDAO ruleResponsibilityDAO;
100 
101     public RuleResponsibilityDAO getRuleResponsibilityDAO() {
102         return ruleResponsibilityDAO;
103     }
104 
105     public RuleBaseValues getRuleByName(String name) {
106         return ruleDAO.findRuleBaseValuesByName(name);
107     }
108 
109     public RuleBaseValues findDefaultRuleByRuleTemplateId(Long ruleTemplateId){
110         return this.ruleDAO.findDefaultRuleByRuleTemplateId(ruleTemplateId);
111     }
112     public void setRuleResponsibilityDAO(RuleResponsibilityDAO ruleResponsibilityDAO) {
113         this.ruleResponsibilityDAO = ruleResponsibilityDAO;
114     }
115 
116     public void save2(RuleBaseValues ruleBaseValues) throws Exception {
117         save2(ruleBaseValues, null, true);
118     }
119 
120     public void save2(RuleBaseValues ruleBaseValues, RuleDelegation ruleDelegation, boolean saveDelegations) throws Exception {
121         if (ruleBaseValues.getPreviousVersionId() != null) {
122             RuleBaseValues oldRule = findRuleBaseValuesById(ruleBaseValues.getPreviousVersionId());
123             ruleBaseValues.setPreviousVersion(oldRule);
124             ruleBaseValues.setCurrentInd(Boolean.FALSE);
125             ruleBaseValues.setVersionNbr(getNextVersionNumber(oldRule));
126         }
127         if (ruleBaseValues.getVersionNbr() == null) {
128             ruleBaseValues.setVersionNbr(Integer.valueOf(0));
129         }
130         if (ruleBaseValues.getCurrentInd() == null) {
131             ruleBaseValues.setCurrentInd(Boolean.FALSE);
132         }
133         // iterate through all associated responsibilities, and if they are unsaved (responsibilityId is null)
134         // set a new id on them, and recursively save any associated delegation rules
135         for (Object element : ruleBaseValues.getResponsibilities()) {
136             RuleResponsibility responsibility = (RuleResponsibility) element;
137             if (responsibility.getResponsibilityId() == null) {
138                 responsibility.setResponsibilityId(getResponsibilityIdService().getNewResponsibilityId());
139             }
140             if (saveDelegations) {
141                 for (Object element2 : responsibility.getDelegationRules()) {
142                     RuleDelegation localRuleDelegation = (RuleDelegation) element2;
143                     save2(localRuleDelegation.getDelegationRuleBaseValues(), localRuleDelegation, true);
144                 }
145             }
146         }
147         validate2(ruleBaseValues, ruleDelegation, null);
148         getRuleDAO().save(ruleBaseValues);
149     }
150 
151     public void makeCurrent(String documentId) {
152         makeCurrent(findByDocumentId(documentId));
153     }
154 
155     public void makeCurrent(List rules) {
156         PerformanceLogger performanceLogger = new PerformanceLogger();
157 
158         boolean isGenerateRuleArs = true;
159         String generateRuleArs = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KEWConstants.KEW_NAMESPACE, KNSConstants.DetailTypes.RULE_DETAIL_TYPE, KEWConstants.RULE_GENERATE_ACTION_REQESTS_IND);
160         if (!StringUtils.isBlank(generateRuleArs)) {
161             isGenerateRuleArs = KEWConstants.YES_RULE_CHANGE_AR_GENERATION_VALUE.equalsIgnoreCase(generateRuleArs);
162         }
163         Set responsibilityIds = new HashSet();
164         HashMap rulesToSave = new HashMap();
165 
166         Collections.sort(rules, new RuleDelegationSorter());
167         boolean delegateFirst = false;
168         for (Iterator iter = rules.iterator(); iter.hasNext();) {
169             RuleBaseValues rule = (RuleBaseValues) iter.next();
170 
171             performanceLogger.log("Preparing rule: " + rule.getDescription());
172 
173             rule.setCurrentInd(Boolean.TRUE);
174             Timestamp date = new Timestamp(System.currentTimeMillis());
175             rule.setActivationDate(date);
176             try {
177                 rule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
178             } catch (Exception e) {
179                 LOG.error("Parse Exception", e);
180             }
181             rulesToSave.put(rule.getRuleBaseValuesId(), rule);
182             RuleBaseValues oldRule = rule.getPreviousVersion();
183             if (oldRule != null) {
184                 performanceLogger.log("Setting previous rule: " + oldRule.getRuleBaseValuesId() + " to non current.");
185 
186                 oldRule.setCurrentInd(Boolean.FALSE);
187                 oldRule.setDeactivationDate(date);
188                 rulesToSave.put(oldRule.getRuleBaseValuesId(), oldRule);
189                 if (!delegateFirst) {
190                     responsibilityIds.addAll(getResponsibilityIdsFromGraph(oldRule, isGenerateRuleArs));
191                 }
192                 //TODO if more than one delegate is edited from the create delegation screen (which currently can not happen), then this logic will not work.
193                 if (rule.getDelegateRule().booleanValue() && rule.getPreviousVersionId() != null) {
194                     delegateFirst = true;
195                 }
196 
197                 List oldDelegationRules = findOldDelegationRules(oldRule, rule, performanceLogger);
198                 for (Iterator iterator = oldDelegationRules.iterator(); iterator.hasNext();) {
199                     RuleBaseValues delegationRule = (RuleBaseValues) iterator.next();
200 
201                     performanceLogger.log("Setting previous delegation rule: " + delegationRule.getRuleBaseValuesId() + "to non current.");
202 
203                     delegationRule.setCurrentInd(Boolean.FALSE);
204                     rulesToSave.put(delegationRule.getRuleBaseValuesId(), delegationRule);
205                     responsibilityIds.addAll(getResponsibilityIdsFromGraph(delegationRule, isGenerateRuleArs));
206                 }
207             }
208             for (Object element : rule.getResponsibilities()) {
209                 RuleResponsibility responsibility = (RuleResponsibility) element;
210                 for (Object element2 : responsibility.getDelegationRules()) {
211                     RuleDelegation delegation = (RuleDelegation) element2;
212 
213                     delegation.getDelegationRuleBaseValues().setCurrentInd(Boolean.TRUE);
214                     RuleBaseValues delegatorRule = delegation.getDelegationRuleBaseValues();
215 
216                     performanceLogger.log("Setting delegate rule: " + delegatorRule.getDescription() + " to current.");
217                     if (delegatorRule.getActivationDate() == null) {
218                         delegatorRule.setActivationDate(date);
219                     }
220                     try {
221                         delegatorRule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
222                     } catch (Exception e) {
223                         LOG.error("Parse Exception", e);
224                     }
225                     rulesToSave.put(delegatorRule.getRuleBaseValuesId(), delegatorRule);
226                 }
227             }
228         }
229         Map<String, Long> notifyMap = new HashMap<String, Long>();
230         for (Iterator iterator = rulesToSave.values().iterator(); iterator.hasNext();) {
231             RuleBaseValues rule = (RuleBaseValues) iterator.next();
232             getRuleDAO().save(rule);
233             performanceLogger.log("Saved rule: " + rule.getRuleBaseValuesId());
234             installNotification(rule, notifyMap);
235         }
236         LOG.info("Notifying rule cache of "+notifyMap.size()+" cache changes.");
237         for (Object element : notifyMap.values()) {
238             queueRuleCache((Long)element);
239         }
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     public void makeCurrent2(List rules) {
256         PerformanceLogger performanceLogger = new PerformanceLogger();
257 
258         boolean isGenerateRuleArs = true;
259         String generateRuleArs = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KEWConstants.KEW_NAMESPACE, KNSConstants.DetailTypes.RULE_DETAIL_TYPE, KEWConstants.RULE_GENERATE_ACTION_REQESTS_IND);
260         if (!StringUtils.isBlank(generateRuleArs)) {
261             isGenerateRuleArs = KEWConstants.YES_RULE_CHANGE_AR_GENERATION_VALUE.equalsIgnoreCase(generateRuleArs);
262         }
263         Set<Long> responsibilityIds = new HashSet<Long>();
264         Map<Long, RuleBaseValues> rulesToSave = new HashMap<Long, RuleBaseValues>();
265 
266         Collections.sort(rules, new RuleDelegationSorter());
267         for (Iterator iter = rules.iterator(); iter.hasNext();) {
268             RuleBaseValues rule = (RuleBaseValues) iter.next();
269 
270             performanceLogger.log("Preparing rule: " + rule.getDescription());
271 
272             rule.setCurrentInd(Boolean.TRUE);
273             Timestamp date = new Timestamp(System.currentTimeMillis());
274             rule.setActivationDate(date);
275             try {
276                 rule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
277             } catch (Exception e) {
278                 LOG.error("Parse Exception", e);
279             }
280             rulesToSave.put(rule.getRuleBaseValuesId(), rule);
281             RuleBaseValues oldRule = rule.getPreviousVersion();
282             if (oldRule != null) {
283                 performanceLogger.log("Setting previous rule: " + oldRule.getRuleBaseValuesId() + " to non current.");
284                 oldRule.setCurrentInd(Boolean.FALSE);
285                 oldRule.setDeactivationDate(date);
286                 rulesToSave.put(oldRule.getRuleBaseValuesId(), oldRule);
287                 responsibilityIds.addAll(getModifiedResponsibilityIds(oldRule, rule));
288             }
289             for (Object element : rule.getResponsibilities()) {
290                 RuleResponsibility responsibility = (RuleResponsibility) element;
291                 for (Object element2 : responsibility.getDelegationRules()) {
292                     RuleDelegation delegation = (RuleDelegation) element2;
293                     RuleBaseValues delegateRule = delegation.getDelegationRuleBaseValues();
294                     delegateRule.setCurrentInd(Boolean.TRUE);
295                     performanceLogger.log("Setting delegate rule: " + delegateRule.getDescription() + " to current.");
296                     if (delegateRule.getActivationDate() == null) {
297                         delegateRule.setActivationDate(date);
298                     }
299                     try {
300                         delegateRule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
301                     } catch (Exception e) {
302                         LOG.error("Parse Exception", e);
303                     }
304                     rulesToSave.put(delegateRule.getRuleBaseValuesId(), delegateRule);
305                 }
306             }
307         }
308         Map<String, Long> notifyMap = new HashMap<String, Long>();
309         for (RuleBaseValues rule : rulesToSave.values()) {
310             getRuleDAO().save(rule);
311             performanceLogger.log("Saved rule: " + rule.getRuleBaseValuesId());
312             installNotification(rule, notifyMap);
313         }
314         LOG.info("Notifying rule cache of "+notifyMap.size()+" cache changes.");
315         for (Object element : notifyMap.values()) {
316             queueRuleCache((Long)element);
317         }
318         if (isGenerateRuleArs) {
319             getActionRequestService().updateActionRequestsForResponsibilityChange(responsibilityIds);
320         }
321         performanceLogger.log("Time to make current");
322     }
323 
324     /**
325      * makeCurrent(RuleBaseValues) is the version of makeCurrent which is initiated from the new Routing Rule
326      * Maintenance document.  Because of the changes in the data model and the front end here,
327      * this method can be much less complicated than the previous 2!
328      */
329     public void makeCurrent(RuleBaseValues rule, boolean isRetroactiveUpdatePermitted) {
330     	makeCurrent(null, rule, isRetroactiveUpdatePermitted);
331     }
332 
333     public void makeCurrent(RuleDelegation ruleDelegation, boolean isRetroactiveUpdatePermitted) {
334     	makeCurrent(ruleDelegation, ruleDelegation.getDelegationRuleBaseValues(), isRetroactiveUpdatePermitted);
335     }
336 
337     protected void makeCurrent(RuleDelegation ruleDelegation, RuleBaseValues rule, boolean isRetroactiveUpdatePermitted) {
338         PerformanceLogger performanceLogger = new PerformanceLogger();
339 
340         boolean isGenerateRuleArs = false;
341         if (isRetroactiveUpdatePermitted) {
342         	isGenerateRuleArs = true;
343         	String generateRuleArs = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KEWConstants.KEW_NAMESPACE, KNSConstants.DetailTypes.RULE_DETAIL_TYPE, KEWConstants.RULE_GENERATE_ACTION_REQESTS_IND);
344         	if (!StringUtils.isBlank(generateRuleArs)) {
345         		isGenerateRuleArs = KEWConstants.YES_RULE_CHANGE_AR_GENERATION_VALUE.equalsIgnoreCase(generateRuleArs);
346         	}
347         }
348         Set<Long> responsibilityIds = new HashSet<Long>();
349 
350 
351         performanceLogger.log("Preparing rule: " + rule.getDescription());
352 
353         Map<Long, RuleBaseValues> rulesToSave = new HashMap<Long, RuleBaseValues>();
354         generateRuleNameIfNeeded(rule);
355         assignResponsibilityIds(rule);
356         rule.setCurrentInd(Boolean.TRUE);
357         Timestamp date = new Timestamp(System.currentTimeMillis());
358         rule.setActivationDate(date);
359         rule.setDeactivationDate(null);
360 
361         rulesToSave.put(rule.getRuleBaseValuesId(), rule);
362         if (rule.getPreviousVersionId() != null) {
363         	RuleBaseValues oldRule = findRuleBaseValuesById(rule.getPreviousVersionId());
364         	rule.setPreviousVersion(oldRule);
365         }
366         rule.setVersionNbr(0);
367         RuleBaseValues oldRule = rule.getPreviousVersion();
368         if (oldRule != null) {
369         	performanceLogger.log("Setting previous rule: " + oldRule.getRuleBaseValuesId() + " to non current.");
370         	oldRule.setCurrentInd(Boolean.FALSE);
371         	oldRule.setDeactivationDate(date);
372         	rulesToSave.put(oldRule.getRuleBaseValuesId(), oldRule);
373         	responsibilityIds.addAll(getModifiedResponsibilityIds(oldRule, rule));
374         	rule.setVersionNbr(getNextVersionNumber(oldRule));
375         }
376                
377 
378         boolean isRuleDelegation = ruleDelegation != null;
379         
380         Map<String, Long> notifyMap = new HashMap<String, Long>(); 
381         
382         for (RuleBaseValues ruleToSave : rulesToSave.values()) {        	
383         	getRuleDAO().save(ruleToSave);
384         	performanceLogger.log("Saved rule: " + ruleToSave.getRuleBaseValuesId());
385             if (!isRuleDelegation) {
386                 installNotification(ruleToSave, notifyMap);
387             } 
388         }
389         if (isRuleDelegation) {
390         	responsibilityIds.add(ruleDelegation.getResponsibilityId());
391         	ruleDelegation.setDelegateRuleId(rule.getRuleBaseValuesId());
392         	getRuleDelegationService().save(ruleDelegation);
393         	installDelegationNotification(ruleDelegation.getResponsibilityId(), notifyMap);
394         }
395         LOG.info("Notifying rule cache of "+notifyMap.size()+" cache changes.");
396         for (Iterator iterator = notifyMap.values().iterator(); iterator.hasNext();) {
397             if (isRuleDelegation) {
398                 queueDelegationRuleCache((Long)iterator.next());
399             } else {
400                 queueRuleCache((Long)iterator.next());
401             } 
402         }
403         
404 
405         
406         if (isGenerateRuleArs) {
407             getActionRequestService().updateActionRequestsForResponsibilityChange(responsibilityIds);
408         }
409         performanceLogger.log("Time to make current");
410     }
411 
412 
413     private void queueRuleCache(Long ruleId){
414 //      PersistedMessageBO ruleCache = new PersistedMessageBO();
415 //      ruleCache.setQueuePriority(KEWConstants.ROUTE_QUEUE_RULE_CACHE_PRIORITY);
416 //      ruleCache.setQueueDate(new Timestamp(new Date().getTime()));
417 //      ruleCache.setQueueStatus(KEWConstants.ROUTE_QUEUE_QUEUED);
418 //      ruleCache.setRetryCount(new Integer(0));
419 //      ruleCache.setPayload("" + ruleId);
420 //      ruleCache.setProcessorClassName("org.kuali.rice.ksb.cache.RuleCacheProcessor");
421 //      getRouteQueueService().requeueDocument(ruleCache);
422 
423 
424         RuleCacheProcessor ruleCacheProcessor = MessageServiceNames.getRuleCacheProcessor();
425         ruleCacheProcessor.clearRuleFromCache(ruleId);
426 
427     }
428 
429 //  private RouteQueueService getRouteQueueService() {
430 //  return (RouteQueueService)SpringServiceLocator.getService(SpringServiceLocator.ROUTE_QUEUE_SRV);
431 //  }
432 
433     /**
434      * Ensure that we don't have any notification duplication.
435      */
436     private void installNotification(RuleBaseValues rule, Map<String, Long> notifyMap) {
437     	// don't notify the cache if it's a "template" rule!
438     	if (!rule.getTemplateRuleInd()) {
439     		String key = getRuleCacheKey(rule.getRuleTemplateName(), rule.getDocTypeName());
440     		if (!notifyMap.containsKey(key)) {
441     			notifyMap.put(key, rule.getRuleBaseValuesId());
442     		}
443     	}
444     }
445 
446     private void queueDelegationRuleCache(Long responsibilityId) {
447         RuleDelegationCacheProcessor ruleDelegationCacheProcessor = (RuleDelegationCacheProcessor) KsbApiServiceLocator.getMessageHelper().getServiceAsynchronously(new QName("RuleDelegationCacheProcessorService"), null, null, null, null);
448         ruleDelegationCacheProcessor.clearRuleDelegationFromCache(responsibilityId);    	
449     }
450     
451     private void installDelegationNotification(Long responsibilityId, Map<String, Long> notifyMap) {
452     	// don't notify the cache if it's a "template" rule!
453     	String key = getRuleDlgnCacheKey(responsibilityId);
454     	if (!notifyMap.containsKey(key)) {
455     		notifyMap.put(key, responsibilityId);
456     	}
457     }
458 
459 	protected String getRuleDlgnCacheKey(Long responsibilityId) {
460 		return "RuleDlgnCache:" + responsibilityId;
461 	}
462 	
463     public RuleBaseValues getParentRule(Long ruleBaseValuesId) {
464         return getRuleDAO().getParentRule(ruleBaseValuesId);
465     }
466 
467     public void notifyCacheOfDocumentTypeChange(DocumentType documentType) {
468         DocumentType rootDocumentType = KEWServiceLocator.getDocumentTypeService().findRootDocumentType(documentType);
469         notifyCacheOfDocumentTypeChangeFromRoot(rootDocumentType, documentType);
470         notifyCacheOfDocumentTypeChangeFromParent(documentType);
471     }
472 
473     /**
474      * Flushes rules cached for the given DocumentType and then recursivley flushes rules cached
475      * for all children DocumentTypes.
476      */
477     protected void notifyCacheOfDocumentTypeChangeFromParent(DocumentType documentType) {
478         flushDocumentTypeFromCache(documentType.getName());
479         for (Iterator iter = documentType.getChildrenDocTypes().iterator(); iter.hasNext();) {
480             notifyCacheOfDocumentTypeChangeFromParent((DocumentType) iter.next());
481         }
482     }
483 
484     /**
485      * Flushes rules cached from the root of the DocumentType hierarchy (at the given root DocumentType).  Stops
486      * when it hits the given DocumentType.  This method exists because of the nature of
487      * DocumentTypeService.findRootDocumentType(...).  Whenever we have a modification to child document type
488      * and we call findRootDocumentType(...) on it, we end up getting back a version of the root document type
489      * which is cached in the OJB transaction cache and doesn't have the appropriate child document type attached.
490      * A better way to handle this would be to go into the DocumentType service and fix how it versions document
491      * types in versionAndSave to prevent this issue from occurring.
492      *
493      * <p>If such a fix was made then we could simply pass the root DocumentType into notifyCacheOfDocumentTypeChange
494      * and be gauranteed that we will see all appropriate children as we call getChildrenDocTypes().
495      *
496      * <p>One last note, we don't necesarily have to stop this cache flusing at the given DocumentType but there's
497      * no reason to duplicate the work that is going to be done in notifyCacheOfDocumentTypeChangeFromParent.
498      */
499     protected void notifyCacheOfDocumentTypeChangeFromRoot(DocumentType rootDocumentType, DocumentType documentType) {
500         if (rootDocumentType.getName().equals(documentType.getName())) {
501             return;
502         }
503         flushDocumentTypeFromCache(rootDocumentType.getName());
504         for (Iterator iter = rootDocumentType.getChildrenDocTypes().iterator(); iter.hasNext();) {
505             notifyCacheOfDocumentTypeChangeFromRoot((DocumentType) iter.next(), documentType);
506         }
507     }
508 
509     public void notifyCacheOfRuleChange(RuleBaseValues rule, DocumentType documentType) {
510         Boolean cachingRules = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(KEWConstants.KEW_NAMESPACE, KNSConstants.DetailTypes.RULE_DETAIL_TYPE, USING_RULE_CACHE_IND);
511 
512         LOG.info("Entering notifyCacheOfRuleChange.  CachingRules: " + cachingRules + " ; ruleID: " + rule.getRuleBaseValuesId());
513         if (!cachingRules.booleanValue()) {
514             return;
515         }
516         // named rules may be templateless, and therefore cannot be mapped into the cache by templatename/doctype key
517         // but we still need to make sure that if there was a previous version with a template, that the cache is blown away
518         String ruleTemplateName = null;
519         if (rule.getRuleTemplate() == null) {
520             if (rule.getPreviousVersion() != null) {
521                 RuleBaseValues prev = rule.getPreviousVersion();
522                 if (prev.getRuleTemplate() != null) {
523                     ruleTemplateName = prev.getRuleTemplate().getName();
524                 }
525             }
526         } else {
527             ruleTemplateName = rule.getRuleTemplate().getName();
528         }
529 
530         if (documentType == null) {
531             documentType = getDocumentTypeService().findByName(rule.getDocTypeName());
532             // if it's a delegate rule, we need to look at the parent's template
533             if (Boolean.TRUE.equals(rule.getDelegateRule())) {
534                 List delegations = getRuleDelegationService().findByDelegateRuleId(rule.getRuleBaseValuesId());
535                 for (Iterator iterator = delegations.iterator(); iterator.hasNext();) {
536                     RuleDelegation ruleDelegation = (RuleDelegation) iterator.next();
537                     RuleBaseValues parentRule = ruleDelegation.getRuleResponsibility().getRuleBaseValues();
538                     if (Boolean.TRUE.equals(parentRule.getCurrentInd())) {
539                         ruleTemplateName = parentRule.getRuleTemplate().getName();
540                         break;
541                     }
542                 }
543             }
544         }
545         flushListFromCache(ruleTemplateName, documentType.getName());
546 
547 //      if (getListFromCache(ruleTemplateName, documentType.getName()) != null) {
548 //      eventGenerator.notify(new CacheDataModifiedEvent(RULE_CACHE_NAME, getRuleCacheKey(ruleTemplateName, documentType.getName())));
549 //      }
550         //}
551         //walk the down the document hierarchy when refreshing the cache. The
552         // rule could be cached through more than one path
553         //HREDOC could be cached under HREDOC.child and HREDOC.child1
554         for (Iterator iter = documentType.getChildrenDocTypes().iterator(); iter.hasNext();) {
555             DocumentType childDocumentType = (DocumentType) iter.next();
556             //SpringServiceLocator.getCacheAdministrator().flushEntry(getRuleCacheKey(ruleTemplateName, childDocumentType.getName()));
557 //          if (getListFromCache(rule.getRuleTemplate().getName(), childDocumentType.getName()) != null) {
558 //          eventGenerator.notify(new CacheDataModifiedEvent(RULE_CACHE_NAME, getRuleCacheKey(rule.getRuleTemplate().getName(), childDocumentType.getName())));
559 //          }
560             notifyCacheOfRuleChange(rule, childDocumentType);
561         }
562         LOG.info("Leaving notifyCacheOfRuleChange.  CachingRules: " + cachingRules + " ; ruleID: " + rule.getRuleBaseValuesId() + " ; documentType: " + documentType.getDocumentTypeId());
563 
564 
565 
566     }
567 
568     /**
569      * Returns the key of the rule cache.
570      */
571     protected String getRuleCacheKey(String ruleTemplateName, String docTypeName) {
572         return "RuleCache:" + ruleTemplateName + "_" + docTypeName;
573     }
574 
575     /*
576      * Return the cache group name for the given DocumentType
577      */
578     protected String getDocumentTypeRuleCacheGroupName(String documentTypeName) {
579         return "DocumentTypeRuleCache:"+documentTypeName;
580     }
581 
582     protected List<RuleBaseValues> getListFromCache(String ruleTemplateName, String documentTypeName) {
583         LOG.debug("Retrieving List of Rules from cache for ruleTemplate='" + ruleTemplateName + "' and documentType='" + documentTypeName + "'");
584         return (List) KsbApiServiceLocator.getCacheAdministrator().getFromCache(getRuleCacheKey(ruleTemplateName, documentTypeName));
585         //return (List) SpringServiceLocator.getCache().getCachedObjectById(RULE_CACHE_NAME, getRuleCacheKey(ruleTemplateName, documentTypeName));
586     }
587 
588     protected void putListInCache(String ruleTemplateName, String documentTypeName, List<RuleBaseValues> rules) {
589         assert(ruleTemplateName != null) : "putListInCache was called with a null ruleTemplateName";
590         LOG.info("Caching " + rules.size() + " rules for ruleTemplate='" + ruleTemplateName + "' and documentType='" + documentTypeName + "'");
591         String groups[] = new String[] { getDocumentTypeRuleCacheGroupName(documentTypeName), RULE_GROUP_CACHE };
592         KsbApiServiceLocator.getCacheAdministrator().putInCache(getRuleCacheKey(ruleTemplateName, documentTypeName), rules, groups);
593     }
594 
595     protected void flushDocumentTypeFromCache(String documentTypeName) {
596         LOG.info("Flushing DocumentType from Cache for the given name: " + documentTypeName);
597         KsbApiServiceLocator.getCacheAdministrator().flushGroup(getDocumentTypeRuleCacheGroupName(documentTypeName));
598     }
599 
600     protected void flushListFromCache(String ruleTemplateName, String documentTypeName) {
601         LOG.info("Flushing rules from Cache for ruleTemplate='" + ruleTemplateName + "' and documentType='" + documentTypeName + "'");
602         KsbApiServiceLocator.getCacheAdministrator().flushEntry(getRuleCacheKey(ruleTemplateName, documentTypeName));
603     }
604 
605     public void flushRuleCache() {
606         LOG.info("Flushing entire Rule Cache.");
607         KsbApiServiceLocator.getCacheAdministrator().flushGroup(RULE_GROUP_CACHE);
608     }
609 
610     private Set getResponsibilityIdsFromGraph(RuleBaseValues rule, boolean isRuleCollecting) {
611         Set responsibilityIds = new HashSet();
612         for (Object element : rule.getResponsibilities()) {
613             RuleResponsibility responsibility = (RuleResponsibility) element;
614             if (isRuleCollecting) {
615                 responsibilityIds.add(responsibility.getResponsibilityId());
616             }
617         }
618         return responsibilityIds;
619     }
620 
621     /**
622      * Returns the responsibility IDs that were modified between the 2 given versions of the rule.  Any added
623      * or removed responsibilities are also included in the returned Set.
624      */
625     private Set<Long> getModifiedResponsibilityIds(RuleBaseValues oldRule, RuleBaseValues newRule) {
626         Map<Long, RuleResponsibility> modifiedResponsibilityMap = new HashMap<Long, RuleResponsibility>();
627         for (Object element : oldRule.getResponsibilities()) {
628             RuleResponsibility responsibility = (RuleResponsibility) element;
629             modifiedResponsibilityMap.put(responsibility.getResponsibilityId(), responsibility);
630         }
631         for (Object element : newRule.getResponsibilities()) {
632             RuleResponsibility responsibility = (RuleResponsibility) element;
633             RuleResponsibility oldResponsibility = modifiedResponsibilityMap.get(responsibility.getResponsibilityId());
634             if (oldResponsibility == null) {
635                 // if there's no old responsibility then it's a new responsibility, add it
636                 modifiedResponsibilityMap.put(responsibility.getResponsibilityId(), responsibility);
637             } else if (!hasResponsibilityChanged(oldResponsibility, responsibility)) {
638                 // if it hasn't been modified, remove it from the collection of modified ids
639                 modifiedResponsibilityMap.remove(responsibility.getResponsibilityId());
640             }
641         }
642         return modifiedResponsibilityMap.keySet();
643     }
644 
645     /**
646      * Determines if the given responsibilities are different or not.
647      */
648     private boolean hasResponsibilityChanged(RuleResponsibility oldResponsibility, RuleResponsibility newResponsibility) {
649         return !ObjectUtils.equals(oldResponsibility.getActionRequestedCd(), newResponsibility.getActionRequestedCd()) ||
650         !ObjectUtils.equals(oldResponsibility.getApprovePolicy(), newResponsibility.getActionRequestedCd()) ||
651         !ObjectUtils.equals(oldResponsibility.getPriority(), newResponsibility.getPriority()) ||
652         !ObjectUtils.equals(oldResponsibility.getRole(), newResponsibility.getRole()) ||
653         !ObjectUtils.equals(oldResponsibility.getRuleResponsibilityName(), newResponsibility.getRuleResponsibilityName()) ||
654         !ObjectUtils.equals(oldResponsibility.getRuleResponsibilityType(), newResponsibility.getRuleResponsibilityType());
655     }
656 
657     /*
658      private Map findOldDelegationRules(RuleBaseValues oldRule, RuleBaseValues newRule, PerformanceLogger performanceLogger) {
659         Map oldRules = new HashMap();
660         performanceLogger.log("Begin to get delegation rules.");
661         for (Iterator iterator = oldRule.getResponsibilities().iterator(); iterator.hasNext();) {
662             RuleResponsibility responsibility = (RuleResponsibility) iterator.next();
663             for (Iterator delIterator = responsibility.getDelegationRules().iterator(); delIterator.hasNext();) {
664                 RuleDelegation ruleDelegation = (RuleDelegation) delIterator.next();
665                 RuleBaseValues delegateRule = ruleDelegation.getDelegationRuleBaseValues();
666                 performanceLogger.log("Found delegate rule: "+ delegateRule.getRuleBaseValuesId());
667                 oldRules.put(ruleDelegation.getDelegateRuleId(), delegateRule);
668             }
669         }
670         performanceLogger.log("Begin removing rule delegations from new rule.");
671         for (Iterator iterator = newRule.getResponsibilities().iterator(); iterator.hasNext();) {
672             RuleResponsibility responsibility = (RuleResponsibility) iterator.next();
673             for (Iterator delIterator = responsibility.getDelegationRules().iterator(); delIterator.hasNext();) {
674                 RuleDelegation ruleDelegation = (RuleDelegation) delIterator.next();
675                 performanceLogger.log("Removing rule delegation: "+ ruleDelegation.getDelegateRuleId()+" from new rule.");
676                 oldRules.remove(ruleDelegation.getDelegateRuleId());
677             }
678         }
679         return oldRules;
680     }
681      */
682     /**
683      * This method will find any old delegation rules on the previous version of the parent rule which are not on the
684      * new version of the rule so that they can be marked non-current.
685      */
686     private List findOldDelegationRules(RuleBaseValues oldRule, RuleBaseValues newRule, PerformanceLogger performanceLogger) {
687         performanceLogger.log("Begin to get delegation rules.");
688         List oldDelegations = getRuleDAO().findOldDelegations(oldRule, newRule);
689         performanceLogger.log("Located "+oldDelegations.size()+" old delegation rules.");
690         return oldDelegations;
691     }
692 
693     public String routeRuleWithDelegate(String documentId, RuleBaseValues parentRule, RuleBaseValues delegateRule, PrincipalContract principal, String annotation, boolean blanketApprove) throws Exception {
694         if (parentRule == null) {
695             throw new IllegalArgumentException("Cannot route a delegate without a parent rule.");
696         }
697         if (parentRule.getDelegateRule().booleanValue()) {
698             throw new IllegalArgumentException("Parent rule cannot be a delegate.");
699         }
700         if (parentRule.getPreviousVersionId() == null && delegateRule.getPreviousVersionId() == null) {
701             throw new IllegalArgumentException("Previous rule version required.");
702         }
703 
704         // if the parent rule is new, unsaved, then save it
705 //      boolean isRoutingParent = parentRule.getRuleBaseValuesId() == null;
706 //      if (isRoutingParent) {
707 //      // it's very important that we do not save delegations here (that's what the false parameter is for)
708 //      // this is because, if we save the delegations, the existing delegations on our parent rule will become
709 //      // saved as "non current" before the rule is approved!!!
710 //      save2(parentRule, null, false);
711 //      //save2(parentRule, null, true);
712 //      }
713 
714         // XXX: added when the RuleValidation stuff was added, basically we just need to get the RuleDelegation
715         // that points to our delegate rule, this rule code is scary...
716         RuleDelegation ruleDelegation = getRuleDelegation(parentRule, delegateRule);
717 
718         save2(delegateRule, ruleDelegation, true);
719 
720 //      if the parent rule is new, unsaved, then save it
721         // It's important to save the parent rule after the delegate rule is saved, that way we can ensure that any new rule
722         // delegations have a valid, saved, delegation rule to point to (otherwise we end up with a null constraint violation)
723         boolean isRoutingParent = parentRule.getRuleBaseValuesId() == null;
724         if (isRoutingParent) {
725             // it's very important that we do not save delegations here (that's what the false parameter is for)
726             // this is because, if we save the delegations, the existing delegations on our parent rule will become
727             // saved as "non current" before the rule is approved!!!
728             save2(parentRule, null, false);
729             //save2(parentRule, null, true);
730         }
731 
732         WorkflowDocument workflowDocument = null;
733         if (documentId != null) {
734             workflowDocument = WorkflowDocument.loadDocument(principal.getPrincipalId(), documentId);
735         } else {
736             List rules = new ArrayList();
737             rules.add(delegateRule);
738             rules.add(parentRule);
739             workflowDocument = WorkflowDocument.createDocument(principal.getPrincipalId(), getRuleDocmentTypeName(rules));
740         }
741         workflowDocument.setTitle(generateTitle(parentRule, delegateRule));
742         delegateRule.setDocumentId(workflowDocument.getDocumentId());
743         workflowDocument.addAttributeDefinition(new RuleRoutingDefinition(parentRule.getDocTypeName()));
744         getRuleDAO().save(delegateRule);
745         if (isRoutingParent) {
746             parentRule.setDocumentId(workflowDocument.getDocumentId());
747             getRuleDAO().save(parentRule);
748         }
749         if (blanketApprove) {
750             workflowDocument.blanketApprove(annotation);
751         } else {
752             workflowDocument.routeDocument(annotation);
753         }
754         return workflowDocument.getDocumentId();
755     }
756 
757     /**
758      * Gets the RuleDelegation object from the parentRule that points to the delegateRule.
759      */
760     private RuleDelegation getRuleDelegation(RuleBaseValues parentRule, RuleBaseValues delegateRule) throws Exception {
761         for (Object element : parentRule.getResponsibilities()) {
762             RuleResponsibility responsibility = (RuleResponsibility) element;
763             for (Object element2 : responsibility.getDelegationRules()) {
764                 RuleDelegation ruleDelegation = (RuleDelegation) element2;
765                 // they should be the same object in memory
766                 if (ruleDelegation.getDelegationRuleBaseValues().equals(delegateRule)) {
767                     return ruleDelegation;
768                 }
769             }
770         }
771         return null;
772     }
773 
774     private String generateTitle(RuleBaseValues parentRule, RuleBaseValues delegateRule) {
775         StringBuffer title = new StringBuffer();
776         if (delegateRule.getPreviousVersionId() != null) {
777             title.append("Editing Delegation Rule '").append(delegateRule.getDescription()).append("' on '");
778         } else {
779             title.append("Adding Delegation Rule '").append(delegateRule.getDescription()).append("' to '");
780         }
781         title.append(parentRule.getDescription()).append("'");
782         return title.toString();
783     }
784 
785     public void validate(RuleBaseValues ruleBaseValues, List errors) {
786         if (errors == null) {
787             errors = new ArrayList();
788         }
789         if (getDocumentTypeService().findByName(ruleBaseValues.getDocTypeName()) == null) {
790             errors.add(new WorkflowServiceErrorImpl("Document Type Invalid", "doctype.documenttypeservice.doctypename.required"));
791         }
792         if (ruleBaseValues.getToDate().before(ruleBaseValues.getFromDate())) {
793             errors.add(new WorkflowServiceErrorImpl("From Date is later than to date", "routetemplate.ruleservice.daterange.fromafterto"));
794         }
795         if (ruleBaseValues.getActiveInd() == null) {
796             errors.add(new WorkflowServiceErrorImpl("Active Indicator is required", "routetemplate.ruleservice.activeind.required"));
797         }
798         if (ruleBaseValues.getDescription() == null || ruleBaseValues.getDescription().equals("")) {
799             errors.add(new WorkflowServiceErrorImpl("Description is required", "routetemplate.ruleservice.description.required"));
800         }
801         if (ruleBaseValues.getForceAction() == null) {
802             errors.add(new WorkflowServiceErrorImpl("Force Action is required", "routetemplate.ruleservice.forceAction.required"));
803         }
804         if (ruleBaseValues.getResponsibilities().isEmpty()) {
805             errors.add(new WorkflowServiceErrorImpl("A responsibility is required", "routetemplate.ruleservice.responsibility.required"));
806         } else {
807             for (Object element : ruleBaseValues.getResponsibilities()) {
808                 RuleResponsibility responsibility = (RuleResponsibility) element;
809                 if (responsibility.getRuleResponsibilityName() != null && KEWConstants.RULE_RESPONSIBILITY_GROUP_ID.equals(responsibility.getRuleResponsibilityType())) {
810                     if (getIdentityManagementService().getGroup(responsibility.getRuleResponsibilityName()) == null) {
811                         errors.add(new WorkflowServiceErrorImpl("Workgroup is invalid", "routetemplate.ruleservice.workgroup.invalid"));
812                     }
813                 } else if (responsibility.getPrincipal() == null && responsibility.getRole() == null) {
814                     errors.add(new WorkflowServiceErrorImpl("User is invalid", "routetemplate.ruleservice.user.invalid"));
815                 }
816             }
817         }
818         if (!errors.isEmpty()) {
819             throw new WorkflowServiceErrorException("RuleBaseValues validation errors", errors);
820         }
821     }
822 
823     public void validate2(RuleBaseValues ruleBaseValues, RuleDelegation ruleDelegation, List errors) {
824         if (errors == null) {
825             errors = new ArrayList();
826         }
827         if (getDocumentTypeService().findByName(ruleBaseValues.getDocTypeName()) == null) {
828             errors.add(new WorkflowServiceErrorImpl("Document Type Invalid", "doctype.documenttypeservice.doctypename.required"));
829             LOG.error("Document Type Invalid");
830         }
831         if (ruleBaseValues.getToDate() == null) {
832             try {
833                 ruleBaseValues.setToDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
834             } catch (ParseException e) {
835                 LOG.error("Error date-parsing default date");
836                 throw new WorkflowServiceErrorException("Error parsing default date.", e);
837             }
838         }
839         if (ruleBaseValues.getFromDate() == null) {
840             ruleBaseValues.setFromDate(new Timestamp(System.currentTimeMillis()));
841         }
842         if (ruleBaseValues.getToDate().before(ruleBaseValues.getFromDate())) {
843             errors.add(new WorkflowServiceErrorImpl("From Date is later than to date", "routetemplate.ruleservice.daterange.fromafterto"));
844             LOG.error("From Date is later than to date");
845         }
846         if (ruleBaseValues.getActiveInd() == null) {
847             errors.add(new WorkflowServiceErrorImpl("Active Indicator is required", "routetemplate.ruleservice.activeind.required"));
848             LOG.error("Active Indicator is missing");
849         }
850         if (ruleBaseValues.getDescription() == null || ruleBaseValues.getDescription().equals("")) {
851             errors.add(new WorkflowServiceErrorImpl("Description is required", "routetemplate.ruleservice.description.required"));
852             LOG.error("Description is missing");
853         }
854         if (ruleBaseValues.getForceAction() == null) {
855             errors.add(new WorkflowServiceErrorImpl("Force Action is required", "routetemplate.ruleservice.forceAction.required"));
856             LOG.error("Force Action is missing");
857         }
858 
859         for (Object element : ruleBaseValues.getResponsibilities()) {
860             RuleResponsibility responsibility = (RuleResponsibility) element;
861             if (responsibility.getRuleResponsibilityName() != null && KEWConstants.RULE_RESPONSIBILITY_GROUP_ID.equals(responsibility.getRuleResponsibilityType())) {
862                 if (getIdentityManagementService().getGroup(responsibility.getRuleResponsibilityName()) == null) {
863                     errors.add(new WorkflowServiceErrorImpl("Workgroup is invalid", "routetemplate.ruleservice.workgroup.invalid"));
864                     LOG.error("Workgroup is invalid");
865                 }
866             } else if (responsibility.getPrincipal() == null && responsibility.getRole() == null) {
867                 errors.add(new WorkflowServiceErrorImpl("User is invalid", "routetemplate.ruleservice.user.invalid"));
868                 LOG.error("User is invalid");
869             } else if (responsibility.isUsingRole()) {
870                 if (responsibility.getApprovePolicy() == null || !(responsibility.getApprovePolicy().equals(KEWConstants.APPROVE_POLICY_ALL_APPROVE) || responsibility.getApprovePolicy().equals(KEWConstants.APPROVE_POLICY_FIRST_APPROVE))) {
871                     errors.add(new WorkflowServiceErrorImpl("Approve Policy is Invalid", "routetemplate.ruleservice.approve.policy.invalid"));
872                     LOG.error("Approve Policy is Invalid");
873                 }
874             }
875         }
876 
877         if (ruleBaseValues.getRuleTemplate() != null) {
878             for (Object element : ruleBaseValues.getRuleTemplate().getActiveRuleTemplateAttributes()) {
879                 RuleTemplateAttribute templateAttribute = (RuleTemplateAttribute) element;
880                 if (!templateAttribute.isRuleValidationAttribute()) {
881                     continue;
882                 }
883                 RuleValidationAttribute attribute = templateAttribute.getRuleValidationAttribute();
884                 UserSession userSession = GlobalVariables.getUserSession();
885                 try {
886                     RuleValidationContext validationContext = new RuleValidationContext(ruleBaseValues, ruleDelegation, userSession);
887                     ValidationResults results = attribute.validate(validationContext);
888                     if (results != null && !results.getValidationResults().isEmpty()) {
889                         errors.add(results);
890                     }
891                 } catch (Exception e) {
892                     if (e instanceof RuntimeException) {
893                         throw (RuntimeException)e;
894                     }
895                     throw new RuntimeException("Problem validation rule.", e);
896                 }
897 
898             }
899         }
900         if (ruleBaseValues.getRuleExpressionDef() != null) {
901             // rule expressions do not require parse-/save-time validation
902         }
903 
904         if (!errors.isEmpty()) {
905             throw new WorkflowServiceErrorException("RuleBaseValues validation errors", errors);
906         }
907     }
908 
909     public List findByDocumentId(String documentId) {
910         return getRuleDAO().findByDocumentId(documentId);
911     }
912 
913     public List search(String docTypeName, Long ruleId, Long ruleTemplateId, String ruleDescription, String groupId, String principalId,
914             Boolean delegateRule, Boolean activeInd, Map extensionValues, String workflowIdDirective) {
915         return getRuleDAO().search(docTypeName, ruleId, ruleTemplateId, ruleDescription, groupId, principalId, delegateRule,
916                 activeInd, extensionValues, workflowIdDirective);
917     }
918 
919     public List search(String docTypeName, String ruleTemplateName, String ruleDescription, String groupId, String principalId,
920             Boolean workgroupMember, Boolean delegateRule, Boolean activeInd, Map extensionValues, Collection<String> actionRequestCodes) {
921 
922         if ( (StringUtils.isEmpty(docTypeName)) &&
923                 (StringUtils.isEmpty(ruleTemplateName)) &&
924                 (StringUtils.isEmpty(ruleDescription)) &&
925                 (StringUtils.isEmpty(groupId)) &&
926                 (StringUtils.isEmpty(principalId)) &&
927                 (extensionValues.isEmpty()) &&
928                 (actionRequestCodes.isEmpty()) ) {
929             // all fields are empty
930             throw new IllegalArgumentException("At least one criterion must be sent");
931         }
932 
933         RuleTemplate ruleTemplate = getRuleTemplateService().findByRuleTemplateName(ruleTemplateName);
934         Long ruleTemplateId = null;
935         if (ruleTemplate != null) {
936             ruleTemplateId = ruleTemplate.getRuleTemplateId();
937         }
938 
939         if ( ( (extensionValues != null) && (!extensionValues.isEmpty()) ) &&
940                 (ruleTemplateId == null) ) {
941             // cannot have extensions without a correct template
942             throw new IllegalArgumentException("A Rule Template Name must be given if using Rule Extension values");
943         }
944 
945         Collection<String> workgroupIds = new ArrayList<String>();
946         if (principalId != null) {
947             KEWServiceLocator.getIdentityHelperService().validatePrincipalId(principalId);
948             if ( (workgroupMember == null) || (workgroupMember.booleanValue()) ) {
949         		workgroupIds = getIdentityManagementService().getGroupIdsForPrincipal(principalId);
950         	} else {
951         		// user was passed but workgroups should not be parsed... do nothing
952         	}
953         } else if (groupId != null) {
954         	Group group = KEWServiceLocator.getIdentityHelperService().getGroup(groupId);
955         	if (group == null) {
956         		throw new IllegalArgumentException("Group does not exist in for given group id: " + groupId);
957         	} else  {
958         		workgroupIds.add(group.getId());
959         	}
960         }
961 
962         return getRuleDAO().search(docTypeName, ruleTemplateId, ruleDescription, workgroupIds, principalId,
963                 delegateRule,activeInd, extensionValues, actionRequestCodes);
964     }
965 
966     public void delete(Long ruleBaseValuesId) {
967         getRuleDAO().delete(ruleBaseValuesId);
968     }
969 
970     public RuleBaseValues findRuleBaseValuesById(Long ruleBaseValuesId) {
971         return getRuleDAO().findRuleBaseValuesById(ruleBaseValuesId);
972     }
973 
974     public RuleResponsibility findRuleResponsibility(Long responsibilityId) {
975         return getRuleDAO().findRuleResponsibility(responsibilityId);
976     }
977 
978     public List fetchAllCurrentRulesForTemplateDocCombination(String ruleTemplateName, String documentType, boolean ignoreCache) {
979         PerformanceLogger performanceLogger = new PerformanceLogger();
980         Boolean cachingRules = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(KEWConstants.KEW_NAMESPACE, KNSConstants.DetailTypes.RULE_DETAIL_TYPE, USING_RULE_CACHE_IND);
981         if (cachingRules.booleanValue()) {
982             //Cache cache = SpringServiceLocator.getCache();
983             List<RuleBaseValues> rules = getListFromCache(ruleTemplateName, documentType);
984             if (rules != null && !ignoreCache) {
985                 performanceLogger.log("Time to fetchRules by template " + ruleTemplateName + " cached.");
986                 return rules;
987             }
988             RuleTemplate ruleTemplate = getRuleTemplateService().findByRuleTemplateName(ruleTemplateName);
989             if (ruleTemplate == null) {
990                 return Collections.EMPTY_LIST;
991             }
992             Long ruleTemplateId = ruleTemplate.getRuleTemplateId();
993             //RuleListCache translatedRules = new RuleListCache();
994             //translatedRules.setId(getRuleCacheKey(ruleTemplateName, documentType));
995             rules = getRuleDAO().fetchAllCurrentRulesForTemplateDocCombination(ruleTemplateId, getDocGroupAndTypeList(documentType));
996             //translatedRules.addAll(rules);
997             putListInCache(ruleTemplateName, documentType, rules);
998             //cache.add(RULE_CACHE_NAME, translatedRules);
999             performanceLogger.log("Time to fetchRules by template " + ruleTemplateName + " cache refreshed.");
1000             return rules;
1001         } else {
1002             Long ruleTemplateId = getRuleTemplateService().findByRuleTemplateName(ruleTemplateName).getRuleTemplateId();
1003             performanceLogger.log("Time to fetchRules by template " + ruleTemplateName + " not caching.");
1004             return getRuleDAO().fetchAllCurrentRulesForTemplateDocCombination(ruleTemplateId, getDocGroupAndTypeList(documentType));
1005         }
1006     }
1007 
1008     public List fetchAllCurrentRulesForTemplateDocCombination(String ruleTemplateName, String documentType) {
1009         return fetchAllCurrentRulesForTemplateDocCombination(ruleTemplateName, documentType, false);
1010     }
1011 
1012     public List fetchAllCurrentRulesForTemplateDocCombination(String ruleTemplateName, String documentType, Timestamp effectiveDate){
1013         Long ruleTemplateId = getRuleTemplateService().findByRuleTemplateName(ruleTemplateName).getRuleTemplateId();
1014         PerformanceLogger performanceLogger = new PerformanceLogger();
1015         performanceLogger.log("Time to fetchRules by template " + ruleTemplateName + " not caching.");
1016         return getRuleDAO().fetchAllCurrentRulesForTemplateDocCombination(ruleTemplateId, getDocGroupAndTypeList(documentType), effectiveDate);
1017     }
1018     public List fetchAllRules(boolean currentRules) {
1019         return getRuleDAO().fetchAllRules(currentRules);
1020     }
1021 
1022     private List getDocGroupAndTypeList(String documentType) {
1023         List docTypeList = new ArrayList();
1024         DocumentTypeService docTypeService = (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE);
1025         DocumentType docType = docTypeService.findByName(documentType);
1026         while (docType != null) {
1027             docTypeList.add(docType.getName());
1028             docType = docType.getParentDocType();
1029         }
1030         return docTypeList;
1031     }
1032 
1033     private Integer getNextVersionNumber(RuleBaseValues currentRule) {
1034         List candidates = new ArrayList();
1035         candidates.add(currentRule.getVersionNbr());
1036         List pendingRules = ruleDAO.findByPreviousVersionId(currentRule.getRuleBaseValuesId());
1037         for (Iterator iterator = pendingRules.iterator(); iterator.hasNext();) {
1038             RuleBaseValues pendingRule = (RuleBaseValues) iterator.next();
1039             candidates.add(pendingRule.getVersionNbr());
1040         }
1041         Collections.sort(candidates);
1042         Integer maxVersionNumber = (Integer) candidates.get(candidates.size() - 1);
1043         if (maxVersionNumber == null) {
1044             return Integer.valueOf(0);
1045         }
1046         return Integer.valueOf(maxVersionNumber.intValue() + 1);
1047     }
1048 
1049     /**
1050      * Determines if the given rule is locked for routing.
1051      *
1052      * In the case of a root rule edit, this method will take the rule id of the rule being edited.
1053      *
1054      * In the case of a new delegate rule or a delegate rule edit, this method will take the id of it's parent.
1055      */
1056     public String isLockedForRouting(Long currentRuleBaseValuesId) {
1057         // checks for any other versions of the given rule, essentially, if this is a rule edit we want to see how many other
1058         // pending edits are out there
1059         List pendingRules = ruleDAO.findByPreviousVersionId(currentRuleBaseValuesId);
1060         boolean isDead = true;
1061         for (Iterator iterator = pendingRules.iterator(); iterator.hasNext();) {
1062             RuleBaseValues pendingRule = (RuleBaseValues) iterator.next();
1063 
1064             if (pendingRule.getDocumentId() != null && StringUtils.isNotBlank(pendingRule.getDocumentId())) {
1065                 DocumentRouteHeaderValue routeHeader = getRouteHeaderService().getRouteHeader(pendingRule.getDocumentId());
1066                 // the pending edit is considered dead if it's been disapproved or cancelled and we are allowed to proceed with our own edit
1067                 isDead = routeHeader.isDisaproved() || routeHeader.isCanceled();
1068                 if (!isDead) {
1069                     return pendingRule.getDocumentId();
1070                 }
1071             }
1072 
1073             for (Object element : pendingRule.getResponsibilities()) {
1074                 RuleResponsibility responsibility = (RuleResponsibility) element;
1075                 for (Object element2 : responsibility.getDelegationRules()) {
1076                     RuleDelegation delegation = (RuleDelegation) element2;
1077                     List pendingDelegateRules = ruleDAO.findByPreviousVersionId(delegation.getDelegationRuleBaseValues().getRuleBaseValuesId());
1078                     for (Iterator iterator3 = pendingDelegateRules.iterator(); iterator3.hasNext();) {
1079                         RuleBaseValues pendingDelegateRule = (RuleBaseValues) iterator3.next();
1080                         if (pendingDelegateRule.getDocumentId() != null && StringUtils.isNotBlank(pendingDelegateRule.getDocumentId())) {
1081                             DocumentRouteHeaderValue routeHeader = getRouteHeaderService().getRouteHeader(pendingDelegateRule.getDocumentId());
1082                             isDead = routeHeader.isDisaproved() || routeHeader.isCanceled();
1083                             if (!isDead) {
1084                                 return pendingDelegateRule.getDocumentId();
1085                             }
1086                         }
1087                     }
1088                 }
1089             }
1090         }
1091         return null;
1092     }
1093 
1094     public RuleBaseValues getParentRule(RuleBaseValues rule) {
1095         if (rule == null || rule.getRuleBaseValuesId() == null) {
1096             throw new IllegalArgumentException("Rule must be non-null with non-null id: " + rule);
1097         }
1098         if (!Boolean.TRUE.equals(rule.getDelegateRule())) {
1099             return null;
1100         }
1101         return getRuleDAO().getParentRule(rule.getRuleBaseValuesId());
1102     }
1103 
1104     /**
1105      * This configuration is currently stored in a system parameter named "CUSTOM_DOCUMENT_TYPES ",
1106      * long term we should come up with a better solution.  The format of this constant is a comma-separated
1107      * list of entries of the following form:
1108      *
1109      * <<name of doc type on rule>>:<<rule template name on rule>>:<<type of rule>>:<<name of document type to use for rule routing>>
1110      *
1111      * Rule type indicates either main or delegation rules.  A main rule is indicated by the character 'M' and a
1112      * delegate rule is indicated by the character 'D'.
1113      *
1114      * So, if you wanted to route "main" rules made for the "MyDocType" document with the rule template name
1115      * "MyRuleTemplate" using the "MyMainRuleDocType" doc type, it would be specified as follows:
1116      *
1117      * MyDocType:MyRuleTemplate:M:MyMainRuleDocType
1118      *
1119      * If you also wanted to route "delegate" rules made for the "MyDocType" document with rule template name
1120      * "MyDelegateTemplate" using the "MyDelegateRuleDocType", you would then set the constant as follows:
1121      *
1122      * MyDocType:MyRuleTemplate:M:MyMainRuleDocType,MyDocType:MyDelegateTemplate:D:MyDelegateRuleDocType
1123      *
1124      * TODO this method ended up being a mess, we should get rid of this as soon as we can
1125      */
1126     public String getRuleDocmentTypeName(List rules) {
1127         if (rules.size() == 0) {
1128             throw new IllegalArgumentException("Cannot determine rule DocumentType for an empty list of rules.");
1129         }
1130         String ruleDocTypeName = null;
1131         RuleRoutingConfig config = RuleRoutingConfig.parse();
1132         // There are 2 cases here
1133         RuleBaseValues firstRule = (RuleBaseValues)rules.get(0);
1134         if (Boolean.TRUE.equals(firstRule.getDelegateRule())) {
1135             // if it's a delegate rule then the list will contain only 2 elements, the first is the delegate rule,
1136             // the second is the parent rule.  In this case just look at the custom routing process for the delegate rule.
1137             ruleDocTypeName = config.getDocumentTypeName(firstRule);
1138         } else {
1139             // if this is a list of parent rules being routed, look at all configued routing types and verify that they are
1140             // all the same, if not throw an exception
1141             String parentRulesDocTypeName = null;
1142             for (Iterator iterator = rules.iterator(); iterator.hasNext();) {
1143                 RuleBaseValues rule = (RuleBaseValues) iterator.next();
1144                 // if it's a delegate rule just skip it
1145                 if  (Boolean.TRUE.equals(rule.getDelegateRule())) {
1146                     continue;
1147                 }
1148                 String currentDocTypeName = config.getDocumentTypeName(rule);
1149                 if (parentRulesDocTypeName == null) {
1150                     parentRulesDocTypeName = currentDocTypeName;
1151                 } else {
1152                     if (!ObjectUtils.equals(currentDocTypeName, parentRulesDocTypeName)) {
1153                         throw new RuntimeException("There are multiple rules being routed and they have different document type definitions!  " + parentRulesDocTypeName + " and " + currentDocTypeName);
1154                     }
1155                 }
1156             }
1157             ruleDocTypeName = parentRulesDocTypeName;
1158         }
1159         if (ruleDocTypeName == null) {
1160             ruleDocTypeName = KEWConstants.DEFAULT_RULE_DOCUMENT_NAME;
1161         }
1162         return ruleDocTypeName;
1163     }
1164 
1165     public void setRuleDAO(RuleDAO ruleDAO) {
1166         this.ruleDAO = ruleDAO;
1167     }
1168 
1169     public RuleDAO getRuleDAO() {
1170         return ruleDAO;
1171     }
1172 
1173     public void deleteRuleResponsibilityById(Long ruleResponsibilityId) {
1174         getRuleResponsibilityDAO().delete(ruleResponsibilityId);
1175     }
1176 
1177     public RuleResponsibility findByRuleResponsibilityId(Long ruleResponsibilityId) {
1178         return getRuleResponsibilityDAO().findByRuleResponsibilityId(ruleResponsibilityId);
1179     }
1180 
1181     public List findRuleBaseValuesByResponsibilityReviewer(String reviewerName, String type) {
1182         return getRuleDAO().findRuleBaseValuesByResponsibilityReviewer(reviewerName, type);
1183     }
1184 
1185     public List findRuleBaseValuesByResponsibilityReviewerTemplateDoc(String ruleTemplateName, String documentType, String reviewerName, String type) {
1186         return getRuleDAO().findRuleBaseValuesByResponsibilityReviewerTemplateDoc(ruleTemplateName, documentType, reviewerName, type);
1187     }
1188 
1189     public RuleTemplateService getRuleTemplateService() {
1190         return (RuleTemplateService) KEWServiceLocator.getService(KEWServiceLocator.RULE_TEMPLATE_SERVICE);
1191     }
1192 
1193     public DocumentTypeService getDocumentTypeService() {
1194         return (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE);
1195     }
1196 
1197     public IdentityManagementService getIdentityManagementService() {
1198         return KimApiServiceLocator.getIdentityManagementService();
1199     }
1200 
1201     public ActionRequestService getActionRequestService() {
1202         return (ActionRequestService) KEWServiceLocator.getService(KEWServiceLocator.ACTION_REQUEST_SRV);
1203     }
1204 
1205     private ResponsibilityIdService getResponsibilityIdService() {
1206         return (ResponsibilityIdService) KEWServiceLocator.getService(KEWServiceLocator.RESPONSIBILITY_ID_SERVICE);
1207     }
1208 
1209     private RuleDelegationService getRuleDelegationService() {
1210         return (RuleDelegationService) KEWServiceLocator.getService(KEWServiceLocator.RULE_DELEGATION_SERVICE);
1211     }
1212 
1213     private RouteHeaderService getRouteHeaderService() {
1214         return (RouteHeaderService) KEWServiceLocator.getService(KEWServiceLocator.DOC_ROUTE_HEADER_SRV);
1215     }
1216 
1217     /**
1218      * A comparator implementation which compares RuleBaseValues and puts all delegate rules first.
1219      */
1220     public class RuleDelegationSorter implements Comparator {
1221         public int compare(Object arg0, Object arg1) {
1222             RuleBaseValues rule1 = (RuleBaseValues) arg0;
1223             RuleBaseValues rule2 = (RuleBaseValues) arg1;
1224 
1225             Integer rule1Value = new Integer((rule1.getDelegateRule().booleanValue() ? 0 : 1));
1226             Integer rule2Value = new Integer((rule2.getDelegateRule().booleanValue() ? 0 : 1));
1227             int value = rule1Value.compareTo(rule2Value);
1228             return value;
1229         }
1230     }
1231 
1232 
1233     public void loadXml(InputStream inputStream, String principalId) {
1234         RuleXmlParser parser = new RuleXmlParser();
1235         try {
1236             parser.parseRules(inputStream);
1237         } catch (Exception e) { //any other exception
1238             LOG.error("Error loading xml file", e);
1239             WorkflowServiceErrorException wsee = new WorkflowServiceErrorException("Error loading xml file", new WorkflowServiceErrorImpl("Error loading xml file", XML_PARSE_ERROR));
1240             wsee.initCause(e);
1241             throw wsee;
1242         }
1243     }
1244 
1245     public Element export(ExportDataSet dataSet) {
1246         RuleXmlExporter exporter = new RuleXmlExporter(XmlConstants.RULE_NAMESPACE);
1247         return exporter.export(dataSet);
1248     }
1249     
1250     @Override
1251 	public boolean supportPrettyPrint() {
1252 		return true;
1253 	}
1254 
1255     protected List<RuleBaseValues> loadRules(List<Long> ruleIds) {
1256         List<RuleBaseValues> rules = new ArrayList<RuleBaseValues>();
1257         for (Long ruleId : ruleIds) {
1258             RuleBaseValues rule = KEWServiceLocator.getRuleService().findRuleBaseValuesById(ruleId);
1259             rules.add(rule);
1260         }
1261         return rules;
1262     }
1263 
1264     /**
1265      * If a rule has been modified and is no longer current since the original request was made, we need to
1266      * be sure to NOT update the rule.
1267      */
1268     protected boolean shouldChangeRuleInvolvement(String documentId, RuleBaseValues rule) {
1269         if (!rule.getCurrentInd()) {
1270             LOG.warn("Rule requested for rule involvement change by document " + documentId + " is no longer current.  Change will not be executed!  Rule id is: " + rule.getRuleBaseValuesId());
1271             return false;
1272         }
1273         String lockingDocumentId = KEWServiceLocator.getRuleService().isLockedForRouting(rule.getRuleBaseValuesId());
1274         if (lockingDocumentId != null) {
1275             LOG.warn("Rule requested for rule involvement change by document " + documentId + " is locked by document " + lockingDocumentId + " and cannot be modified.  " +
1276                     "Change will not be executed!  Rule id is: " + rule.getRuleBaseValuesId());
1277             return false;
1278         }
1279         return true;
1280     }
1281 
1282     protected RuleDelegation getRuleDelegationForDelegateRule(RuleBaseValues rule) {
1283         if (Boolean.TRUE.equals(rule.getDelegateRule())) {
1284             List delegations = getRuleDelegationService().findByDelegateRuleId(rule.getRuleBaseValuesId());
1285             for (Iterator iterator = delegations.iterator(); iterator.hasNext();) {
1286                 RuleDelegation ruleDelegation = (RuleDelegation) iterator.next();
1287                 RuleBaseValues parentRule = ruleDelegation.getRuleResponsibility().getRuleBaseValues();
1288                 if (Boolean.TRUE.equals(parentRule.getCurrentInd())) {
1289                     return ruleDelegation;
1290                 }
1291             }
1292         }
1293         return null;
1294     }
1295 
1296     protected void hookUpDelegateRuleToParentRule(RuleBaseValues newParentRule, RuleBaseValues newDelegationRule, RuleDelegation existingRuleDelegation) {
1297         // hook up parent rule to new rule delegation
1298         boolean foundDelegation = false;
1299         outer:for (RuleResponsibility responsibility : (List<RuleResponsibility>)newParentRule.getResponsibilities()) {
1300             for (RuleDelegation ruleDelegation : (List<RuleDelegation>)responsibility.getDelegationRules()) {
1301                 if (ruleDelegation.getDelegationRuleBaseValues().getRuleBaseValuesId().equals(existingRuleDelegation.getDelegationRuleBaseValues().getRuleBaseValuesId())) {
1302                     ruleDelegation.setDelegationRuleBaseValues(newDelegationRule);
1303                     foundDelegation = true;
1304                     break outer;
1305                 }
1306             }
1307         }
1308         if (!foundDelegation) {
1309             throw new WorkflowRuntimeException("Failed to locate the existing rule delegation with id: " + existingRuleDelegation.getDelegationRuleBaseValues().getRuleBaseValuesId());
1310         }
1311 
1312     }
1313 
1314     protected RuleBaseValues createNewRuleVersion(RuleBaseValues existingRule, String documentId) throws Exception {
1315         RuleBaseValues rule = new RuleBaseValues();
1316         PropertyUtils.copyProperties(rule, existingRule);
1317         rule.setPreviousVersion(existingRule);
1318         rule.setPreviousVersionId(existingRule.getRuleBaseValuesId());
1319         rule.setRuleBaseValuesId(null);
1320         rule.setActivationDate(null);
1321         rule.setDeactivationDate(null);
1322         rule.setVersionNumber(0L);
1323         rule.setDocumentId(documentId);
1324 
1325         // TODO: FIXME: need to copy the rule expression here too?
1326 
1327         rule.setResponsibilities(new ArrayList());
1328         for (RuleResponsibility existingResponsibility : (List<RuleResponsibility>)existingRule.getResponsibilities()) {
1329             RuleResponsibility responsibility = new RuleResponsibility();
1330             PropertyUtils.copyProperties(responsibility, existingResponsibility);
1331             responsibility.setRuleBaseValues(rule);
1332             responsibility.setRuleBaseValuesId(null);
1333             responsibility.setRuleResponsibilityKey(null);
1334             responsibility.setVersionNumber(0L);
1335             rule.getResponsibilities().add(responsibility);
1336 //            responsibility.setDelegationRules(new ArrayList());
1337 //            for (RuleDelegation existingDelegation : (List<RuleDelegation>)existingResponsibility.getDelegationRules()) {
1338 //                RuleDelegation delegation = new RuleDelegation();
1339 //                PropertyUtils.copyProperties(delegation, existingDelegation);
1340 //                delegation.setRuleDelegationId(null);
1341 //                delegation.setRuleResponsibility(responsibility);
1342 //                delegation.setRuleResponsibilityId(null);
1343 //                delegation.setVersionNumber(0L);
1344 //                // it's very important that we do NOT recurse down into the delegation rules and reversion those,
1345 //                // this is important to how rule versioning works
1346 //                responsibility.getDelegationRules().add(delegation);
1347 //            }
1348         }
1349         rule.setRuleExtensions(new ArrayList());
1350         for (RuleExtension existingExtension : (List<RuleExtension>)existingRule.getRuleExtensions()) {
1351             RuleExtension extension = new RuleExtension();
1352             PropertyUtils.copyProperties(extension, existingExtension);
1353             extension.setLockVerNbr(0);
1354             extension.setRuleBaseValues(rule);
1355             extension.setRuleBaseValuesId(null);
1356             extension.setRuleExtensionId(null);
1357             rule.getRuleExtensions().add(extension);
1358             extension.setExtensionValues(new ArrayList<RuleExtensionValue>());
1359             for (RuleExtensionValue existingExtensionValue : extension.getExtensionValues()) {
1360                 RuleExtensionValue extensionValue = new RuleExtensionValue();
1361                 PropertyUtils.copyProperties(extensionValue, existingExtensionValue);
1362                 extensionValue.setExtension(extension);
1363                 extensionValue.setRuleExtensionId(null);
1364                 extensionValue.setLockVerNbr(0);
1365                 extensionValue.setRuleExtensionValueId(null);
1366                 extension.getExtensionValues().add(extensionValue);
1367             }
1368         }
1369         return rule;
1370     }
1371 
1372     private static class RuleVersion {
1373         public RuleBaseValues rule;
1374         public RuleBaseValues parent;
1375         public RuleDelegation delegation;
1376     }
1377 
1378     private static class RuleRoutingConfig {
1379         private List configs = new ArrayList();
1380         public static RuleRoutingConfig parse() {
1381             RuleRoutingConfig config = new RuleRoutingConfig();
1382             String constant = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KEWConstants.KEW_NAMESPACE, KNSConstants.DetailTypes.RULE_DETAIL_TYPE, KEWConstants.RULE_CUSTOM_DOC_TYPES);
1383             if (!StringUtils.isEmpty(constant)) {
1384                 String[] ruleConfigs = constant.split(",");
1385                 for (String ruleConfig : ruleConfigs) {
1386                     String[] configElements = ruleConfig.split(":");
1387                     if (configElements.length != 4) {
1388                         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);
1389                     }
1390                     config.configs.add(configElements);
1391                 }
1392             }
1393             return config;
1394         }
1395         public String getDocumentTypeName(RuleBaseValues rule) {
1396             for (Iterator iterator = configs.iterator(); iterator.hasNext();) {
1397                 String[] configElements = (String[]) iterator.next();
1398                 String docTypeName = configElements[0];
1399                 String ruleTemplateName = configElements[1];
1400                 String type = configElements[2];
1401                 String ruleDocTypeName = configElements[3];
1402                 if (rule.getDocTypeName().equals(docTypeName) && rule.getRuleTemplateName().equals(ruleTemplateName)) {
1403                     if (type.equals("M")) {
1404                         if (Boolean.FALSE.equals(rule.getDelegateRule())) {
1405                             return ruleDocTypeName;
1406                         }
1407                     } else if (type.equals("D")) {
1408                         if (Boolean.TRUE.equals(rule.getDelegateRule())) {
1409                             return ruleDocTypeName;
1410                         }
1411                     } else {
1412                         throw new RuntimeException("Bad rule type '" + type + "' in rule doc type routing config.");
1413                     }
1414                 }
1415             }
1416             return null;
1417         }
1418     }
1419 
1420     public Long getDuplicateRuleId(RuleBaseValues rule) {
1421 
1422     	// TODO: this method is extremely slow, if we could implement a more optimized query here, that would help tremendously
1423 
1424     	List responsibilities = rule.getResponsibilities();
1425     	List extensions = rule.getRuleExtensions();
1426     	String docTypeName = rule.getDocTypeName();
1427     	String ruleTemplateName = rule.getRuleTemplateName();
1428         List rules = fetchAllCurrentRulesForTemplateDocCombination(rule.getRuleTemplateName(), rule.getDocTypeName(), false);
1429         Iterator it = rules.iterator();
1430         while (it.hasNext()) {
1431             RuleBaseValues r = (RuleBaseValues) it.next();
1432             if (ObjectUtils.equals(rule.getActiveInd(), r.getActiveInd()) &&
1433         	ObjectUtils.equals(docTypeName, r.getDocTypeName()) &&
1434                     ObjectUtils.equals(ruleTemplateName, r.getRuleTemplateName()) &&
1435                     ObjectUtils.equals(rule.getRuleExpressionDef(), r.getRuleExpressionDef()) &&
1436                     CollectionUtils.collectionsEquivalent(responsibilities, r.getResponsibilities()) &&
1437                     CollectionUtils.collectionsEquivalent(extensions, r.getRuleExtensions())) {
1438                 // we have a duplicate
1439                 return r.getRuleBaseValuesId();
1440             }
1441         }
1442         return null;
1443     }
1444 
1445     private void generateRuleNameIfNeeded(RuleBaseValues rule) {
1446         if (StringUtils.isBlank(rule.getName())) {
1447         	rule.setName(UUID.randomUUID().toString());
1448         }
1449     }
1450 
1451     private void assignResponsibilityIds(RuleBaseValues rule) {
1452     	for (RuleResponsibility responsibility : rule.getResponsibilities()) {
1453     		if (responsibility.getResponsibilityId() == null) {
1454     			responsibility.setResponsibilityId(KEWServiceLocator.getResponsibilityIdService().getNewResponsibilityId());
1455     		}
1456     	}
1457     }
1458 
1459     public RuleBaseValues saveRule(RuleBaseValues rule, boolean isRetroactiveUpdatePermitted) {
1460     	rule.setPreviousVersionId(rule.getRuleBaseValuesId());
1461 		rule.setPreviousVersion(null);
1462 		rule.setRuleBaseValuesId(null);
1463 		makeCurrent(rule, isRetroactiveUpdatePermitted);
1464 		return rule;
1465     }
1466 
1467     public List<RuleBaseValues> saveRules(List<RuleBaseValues> rulesToSave, boolean isRetroactiveUpdatePermitted) {
1468     	List<RuleBaseValues> savedRules = new ArrayList<RuleBaseValues>();
1469     	for (RuleBaseValues rule : rulesToSave) {
1470     		rule = saveRule(rule, isRetroactiveUpdatePermitted);
1471     		savedRules.add(rule);
1472     	}
1473     	return savedRules;
1474     }
1475 
1476     public RuleDelegation saveRuleDelegation(RuleDelegation ruleDelegation, boolean isRetroactiveUpdatePermitted) {
1477     	RuleBaseValues rule = ruleDelegation.getDelegationRuleBaseValues();
1478 		rule.setPreviousVersionId(rule.getRuleBaseValuesId());
1479 		rule.setPreviousVersion(null);
1480 		rule.setRuleBaseValuesId(null);
1481 		ruleDelegation.setRuleDelegationId(null);
1482 		makeCurrent(ruleDelegation, isRetroactiveUpdatePermitted);
1483 		return ruleDelegation;
1484     }
1485 
1486     public List<RuleDelegation> saveRuleDelegations(List<RuleDelegation> ruleDelegationsToSave, boolean isRetroactiveUpdatePermitted) {
1487     	List<RuleDelegation> savedRuleDelegations = new ArrayList<RuleDelegation>();
1488     	for (RuleDelegation ruleDelegation : ruleDelegationsToSave) {
1489     		ruleDelegation = saveRuleDelegation(ruleDelegation, isRetroactiveUpdatePermitted);
1490     		savedRuleDelegations.add(ruleDelegation);
1491     	}
1492     	return savedRuleDelegations;
1493     }
1494 
1495     public Long findResponsibilityIdForRule(String ruleName, String ruleResponsibilityName, String ruleResponsibilityType) {
1496     	return getRuleDAO().findResponsibilityIdForRule(ruleName, ruleResponsibilityName, ruleResponsibilityType);
1497     }
1498 
1499 }