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.krms.impl.repository; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.core.api.exception.RiceIllegalStateException; 020import org.kuali.rice.krad.data.DataObjectService; 021import org.kuali.rice.krad.data.PersistenceOption; 022import org.kuali.rice.krms.api.repository.action.ActionDefinition; 023import org.kuali.rice.krms.api.repository.rule.RuleDefinition; 024import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition; 025 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.ListIterator; 034import java.util.Map; 035import java.util.Set; 036 037import static org.kuali.rice.krms.impl.repository.BusinessObjectServiceMigrationUtils.findSingleMatching; 038 039public class RuleBoServiceImpl implements RuleBoService { 040 041 private DataObjectService dataObjectService; 042 private KrmsAttributeDefinitionService attributeDefinitionService; 043 044 /** 045 * This overridden creates a KRMS Rule in the repository 046 * 047 * @see org.kuali.rice.krms.impl.repository.RuleBoService#createRule(org.kuali.rice.krms.api.repository.rule.RuleDefinition) 048 */ 049 @Override 050 public RuleDefinition createRule(RuleDefinition rule) { 051 if (rule == null){ 052 throw new IllegalArgumentException("rule is null"); 053 } 054 055 final String nameKey = rule.getName(); 056 final String namespaceKey = rule.getNamespace(); 057 final RuleDefinition existing = getRuleByNameAndNamespace(nameKey, namespaceKey); 058 059 if (existing != null){ 060 throw new IllegalStateException("the rule to create already exists: " + rule); 061 } 062 063 RuleBo ruleBo = RuleBo.from(rule); 064 ruleBo = dataObjectService.save(ruleBo, PersistenceOption.FLUSH); 065 066 return RuleBo.to(ruleBo); 067 } 068 069 /** 070 * This overridden updates an existing Rule in the Repository 071 * 072 * @see org.kuali.rice.krms.impl.repository.RuleBoService#updateRule(org.kuali.rice.krms.api.repository.rule.RuleDefinition) 073 */ 074 @Override 075 public RuleDefinition updateRule(RuleDefinition rule) { 076 if (rule == null){ 077 throw new IllegalArgumentException("rule is null"); 078 } 079 080 // must already exist to be able to update 081 final String ruleIdKey = rule.getId(); 082 final RuleBo existing = dataObjectService.find(RuleBo.class, ruleIdKey); 083 084 if (existing == null) { 085 throw new IllegalStateException("the rule does not exist: " + rule); 086 } 087 088 final RuleDefinition toUpdate; 089 String existingPropositionId = null; 090 091 if (existing.getProposition() != null){ 092 existingPropositionId = existing.getProposition().getId(); 093 } 094 095 if (!existing.getId().equals(rule.getId())){ 096 // if passed in id does not match existing id, correct it 097 final RuleDefinition.Builder builder = RuleDefinition.Builder.create(rule); 098 builder.setId(existing.getId()); 099 toUpdate = builder.build(); 100 } else { 101 toUpdate = rule; 102 } 103 104 RuleBo boToUpdate = RuleBo.from(toUpdate); 105 reconcileActionAttributes(boToUpdate.getActions(), existing.getActions()); 106 107 // update the rule and create new attributes 108 RuleBo updatedData = dataObjectService.save(boToUpdate, PersistenceOption.FLUSH); 109 110 //delete the orphan proposition 111 if (updatedData.getProposition() != null && StringUtils.isNotBlank(existingPropositionId)){ 112 if (!(updatedData.getProposition().getId().equals(existingPropositionId))) { 113 dataObjectService.delete(existing.getProposition()); 114 } 115 } 116 117 return RuleBo.to(updatedData); 118 } 119 120 /** 121 * Transfer any ActionAttributeBos that still apply from the existing actions, while updating their values. 122 * 123 * <p>This method is side effecting, it replaces elements in the passed in toUpdateActionBos collection. </p> 124 * 125 * @param toUpdateActionBos the new ActionBos which will (later) be persisted 126 * @param existingActionBos the ActionBos which have been fetched from the database 127 */ 128 private void reconcileActionAttributes(List<ActionBo> toUpdateActionBos, List<ActionBo> existingActionBos) { 129 for (ActionBo toUpdateAction : toUpdateActionBos) { 130 131 ActionBo matchingExistingAction = findMatchingExistingAction(toUpdateAction, existingActionBos); 132 133 if (matchingExistingAction == null) { continue; } 134 135 ListIterator<ActionAttributeBo> toUpdateAttributesIter = toUpdateAction.getAttributeBos().listIterator(); 136 137 while (toUpdateAttributesIter.hasNext()) { 138 ActionAttributeBo toUpdateAttribute = toUpdateAttributesIter.next(); 139 140 ActionAttributeBo matchingExistingAttribute = 141 findMatchingExistingAttribute(toUpdateAttribute, matchingExistingAction.getAttributeBos()); 142 143 if (matchingExistingAttribute == null) { continue; } 144 145 // set the new value into the existing attribute, then replace the new attribute with the existing one 146 matchingExistingAttribute.setValue(toUpdateAttribute.getValue()); 147 toUpdateAttributesIter.set(matchingExistingAttribute); 148 } 149 } 150 } 151 152 /** 153 * Returns the action in existingActionBos that has the same ID as existingAction, or null if none matches. 154 * 155 * @param toUpdateAction 156 * @param existingActionBos 157 * @return the matching action, or null if none match. 158 */ 159 private ActionBo findMatchingExistingAction(ActionBo toUpdateAction, List<ActionBo> existingActionBos) { 160 for (ActionBo existingAction : existingActionBos) { 161 if (existingAction.getId().equals(toUpdateAction.getId())) { 162 return existingAction; 163 } 164 } 165 166 return null; 167 } 168 169 /** 170 * Returns the attribute in existingAttributeBos that has the same attributeDefinitionId as toUpdateAttribute, or 171 * null if none matches. 172 * 173 * @param toUpdateAttribute 174 * @param existingAttributeBos 175 * @return the matching attribute, or null if none match. 176 */ 177 private ActionAttributeBo findMatchingExistingAttribute(ActionAttributeBo toUpdateAttribute, 178 List<ActionAttributeBo> existingAttributeBos) { 179 for (ActionAttributeBo existingAttribute : existingAttributeBos) { 180 if (existingAttribute.getAttributeDefinitionId().equals(toUpdateAttribute.getAttributeDefinitionId())) { 181 return existingAttribute; 182 } 183 } 184 185 return null; 186 } 187 188 @Override 189 public void deleteRule(String ruleId) { 190 if (ruleId == null) { 191 throw new IllegalArgumentException("ruleId is null"); 192 } 193 194 final RuleDefinition existing = getRuleByRuleId(ruleId); 195 196 if (existing == null) { 197 throw new IllegalStateException("the Rule to delete does not exists: " + ruleId); 198 } 199 200 dataObjectService.delete(from(existing)); 201 } 202 203 /** 204 * This method retrieves a rule from the repository given the rule id. 205 * 206 * @see org.kuali.rice.krms.impl.repository.RuleBoService#getRuleByRuleId(java.lang.String) 207 */ 208 @Override 209 public RuleDefinition getRuleByRuleId(String ruleId) { 210 if (StringUtils.isBlank(ruleId)){ 211 throw new IllegalArgumentException("rule id is null"); 212 } 213 214 RuleBo bo = dataObjectService.find(RuleBo.class, ruleId); 215 216 return RuleBo.to(bo); 217 } 218 219 /** 220 * This method retrieves a rule from the repository given the name of the rule 221 * and namespace. 222 * 223 * @see org.kuali.rice.krms.impl.repository.RuleBoService#getRuleByRuleId(java.lang.String) 224 */ 225 @Override 226 public RuleDefinition getRuleByNameAndNamespace(String name, String namespace) { 227 if (StringUtils.isBlank(name)) { 228 throw new IllegalArgumentException("name is null or blank"); 229 } 230 if (StringUtils.isBlank(namespace)) { 231 throw new IllegalArgumentException("namespace is null or blank"); 232 } 233 234 final Map<String, Object> map = new HashMap<String, Object>(); 235 map.put("name", name); 236 map.put("namespace", namespace); 237 238 RuleBo myRule = findSingleMatching(dataObjectService, RuleBo.class, Collections.unmodifiableMap(map)); 239 240 return RuleBo.to(myRule); 241 } 242 243 /** 244 * Gets a rule attribute by its ID 245 * 246 * @param attrId the rule attribute's ID 247 * @return the rule attribute 248 */ 249 public RuleAttributeBo getRuleAttributeById(String attrId) { 250 if (StringUtils.isBlank(attrId)){ 251 return null; 252 } 253 254 RuleAttributeBo bo = dataObjectService.find(RuleAttributeBo.class, attrId); 255 256 return bo; 257 } 258 259 /** 260 * Converts a immutable {@link RuleDefinition} to its mutable {@link RuleBo} counterpart. 261 * @param rule the immutable object. 262 * @return a {@link RuleBo} the mutable RuleBo. 263 * 264 */ 265 public RuleBo from(RuleDefinition rule) { 266 if (rule == null) { return null; } 267 268 RuleBo ruleBo = new RuleBo(); 269 ruleBo.setName(rule.getName()); 270 ruleBo.setDescription(rule.getDescription()); 271 ruleBo.setNamespace(rule.getNamespace()); 272 ruleBo.setTypeId(rule.getTypeId()); 273 ruleBo.setProposition(PropositionBo.from(rule.getProposition())); 274 ruleBo.setId(rule.getId()); 275 ruleBo.setActive(rule.isActive()); 276 ruleBo.setVersionNumber(rule.getVersionNumber()); 277 ruleBo.setActions(buildActionBoList(rule)); 278 ruleBo.setAttributeBos(buildAttributeBoList(rule)); 279 280 return ruleBo; 281 } 282 283 private Set<RuleAttributeBo> buildAttributeBo(RuleDefinition im) { 284 Set<RuleAttributeBo> attributes = new HashSet<RuleAttributeBo>(); 285 286 // build a map from attribute name to definition 287 Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>(); 288 289 List<KrmsAttributeDefinition> attributeDefinitions = getAttributeDefinitionService().findAttributeDefinitionsByType(im.getTypeId()); 290 291 for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) { 292 attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition); 293 } 294 295 // for each entry, build a RuleAttributeBo and add it to the set 296 if (im.getAttributes() != null) { 297 for (Map.Entry<String,String> entry : im.getAttributes().entrySet()) { 298 KrmsAttributeDefinition attrDef = attributeDefinitionMap.get(entry.getKey()); 299 300 if (attrDef != null) { 301 RuleAttributeBo attributeBo = new RuleAttributeBo(); 302 attributeBo.setRule( RuleBo.from(im) ); 303 attributeBo.setValue(entry.getValue()); 304 attributeBo.setAttributeDefinition(KrmsAttributeDefinitionBo.from(attrDef)); 305 attributes.add( attributeBo ); 306 } else { 307 throw new RiceIllegalStateException("there is no attribute definition with the name '" + 308 entry.getKey() + "' that is valid for the rule type with id = '" + im.getTypeId() +"'"); 309 } 310 } 311 } 312 313 return attributes; 314 } 315 316 private List<RuleAttributeBo> buildAttributeBoList(RuleDefinition im) { 317 List<RuleAttributeBo> attributes = new LinkedList<RuleAttributeBo>(); 318 319 // build a map from attribute name to definition 320 Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>(); 321 322 List<KrmsAttributeDefinition> attributeDefinitions = getAttributeDefinitionService().findAttributeDefinitionsByType(im.getTypeId()); 323 324 for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) { 325 attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition); 326 } 327 328 // for each entry, build a RuleAttributeBo and add it to the set 329 if (im.getAttributes() != null) { 330 for (Map.Entry<String,String> entry : im.getAttributes().entrySet()) { 331 KrmsAttributeDefinition attrDef = attributeDefinitionMap.get(entry.getKey()); 332 333 if (attrDef != null) { 334 RuleAttributeBo attributeBo = new RuleAttributeBo(); 335 attributeBo.setRule(RuleBo.from(im)); 336 attributeBo.setValue(entry.getValue()); 337 attributeBo.setAttributeDefinition(KrmsAttributeDefinitionBo.from(attrDef)); 338 attributes.add( attributeBo ); 339 } else { 340 throw new RiceIllegalStateException("there is no attribute definition with the name '" + 341 entry.getKey() + "' that is valid for the rule type with id = '" + im.getTypeId() +"'"); 342 } 343 } 344 } 345 return attributes; 346 } 347 348 private List<ActionBo> buildActionBoList(RuleDefinition im) { 349 List<ActionBo> actions = new LinkedList<ActionBo>(); 350 351 for (ActionDefinition actionDefinition : im.getActions()) { 352 actions.add(ActionBo.from(actionDefinition)); 353 } 354 355 return actions; 356 } 357 358 /** 359 * Sets the dataObjectService attribute value. 360 * 361 * @param dataObjectService The dataObjectService to set. 362 */ 363 public void setDataObjectService(final DataObjectService dataObjectService) { 364 this.dataObjectService = dataObjectService; 365 } 366 367 /** 368 * Converts a List<RuleBo> to an Unmodifiable List<Rule> 369 * 370 * @param ruleBos a mutable List<RuleBo> to made completely immutable. 371 * @return An unmodifiable List<Rule> 372 */ 373 public List<RuleDefinition> convertListOfBosToImmutables(final Collection<RuleBo> ruleBos) { 374 ArrayList<RuleDefinition> rules = new ArrayList<RuleDefinition>(); 375 376 for (RuleBo bo : ruleBos) { 377 RuleDefinition rule = RuleBo.to(bo); 378 rules.add(rule); 379 } 380 381 return Collections.unmodifiableList(rules); 382 } 383 384 385 protected KrmsAttributeDefinitionService getAttributeDefinitionService() { 386 if (attributeDefinitionService == null) { 387 attributeDefinitionService = KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService(); 388 } 389 390 return attributeDefinitionService; 391 } 392}