View Javadoc

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