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