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