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.provider.repository; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.krms.api.engine.TermResolver; 020import org.kuali.rice.krms.api.repository.RepositoryDataException; 021import org.kuali.rice.krms.api.repository.RuleRepositoryService; 022import org.kuali.rice.krms.api.repository.action.ActionDefinition; 023import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition; 024import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition; 025import org.kuali.rice.krms.api.repository.agenda.AgendaTreeEntryDefinitionContract; 026import org.kuali.rice.krms.api.repository.agenda.AgendaTreeRuleEntry; 027import org.kuali.rice.krms.api.repository.agenda.AgendaTreeSubAgendaEntry; 028import org.kuali.rice.krms.api.repository.context.ContextDefinition; 029import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition; 030import org.kuali.rice.krms.api.repository.rule.RuleDefinition; 031import org.kuali.rice.krms.api.repository.term.TermRepositoryService; 032import org.kuali.rice.krms.api.repository.term.TermResolverDefinition; 033import org.kuali.rice.krms.framework.engine.Action; 034import org.kuali.rice.krms.framework.engine.Agenda; 035import org.kuali.rice.krms.framework.engine.AgendaTree; 036import org.kuali.rice.krms.framework.engine.AgendaTreeEntry; 037import org.kuali.rice.krms.framework.engine.BasicAgendaTree; 038import org.kuali.rice.krms.framework.engine.BasicAgendaTreeEntry; 039import org.kuali.rice.krms.framework.engine.BasicContext; 040import org.kuali.rice.krms.framework.engine.Context; 041import org.kuali.rice.krms.framework.engine.Proposition; 042import org.kuali.rice.krms.framework.engine.Rule; 043import org.kuali.rice.krms.framework.engine.SubAgenda; 044import org.kuali.rice.krms.framework.type.AgendaTypeService; 045import org.kuali.rice.krms.framework.type.TermResolverTypeService; 046import org.kuali.rice.krms.impl.type.AgendaTypeServiceBase; 047import org.kuali.rice.krms.impl.type.KrmsTypeResolver; 048import org.springframework.util.CollectionUtils; 049 050import java.util.ArrayList; 051import java.util.HashMap; 052import java.util.List; 053import java.util.Map; 054 055/** 056 * TODO... 057 * 058 * @author Kuali Rice Team (rice.collab@kuali.org) 059 * 060 */ 061public class RepositoryToEngineTranslatorImpl implements RepositoryToEngineTranslator { 062 063 private RuleRepositoryService ruleRepositoryService; 064 private TermRepositoryService termRepositoryService; 065 private KrmsTypeResolver typeResolver; 066 067 @Override 068 public Context translateContextDefinition(ContextDefinition contextDefinition) { 069 if (contextDefinition == null) { 070 return null; 071 } 072 List<Agenda> agendas = new ArrayList<Agenda>(); 073 for (AgendaDefinition agendaDefinition : contextDefinition.getAgendas()) { 074 Agenda agenda = translateAgendaDefinition(agendaDefinition); 075 agendas.add(agenda); 076 } 077 078 List<TermResolverDefinition> termResolverDefs = 079 getTermRepositoryService().findTermResolversByNamespace(contextDefinition.getNamespace()); 080 081 List<TermResolver<?>> termResolvers = new ArrayList<TermResolver<?>>(); 082 083 if (!CollectionUtils.isEmpty(termResolverDefs)) for (TermResolverDefinition termResolverDef : termResolverDefs) { 084 if (termResolverDef != null) { 085 TermResolver<?> termResolver = translateTermResolver(termResolverDef); 086 if (termResolver != null) termResolvers.add(termResolver); 087 } 088 } 089 090 return new BasicContext(agendas, termResolvers); 091 } 092 093 /** 094 * This method translates a {@link TermResolverDefinition} into a {@link TermResolver} 095 * 096 * @param termResolverDef 097 * @return 098 */ 099 private TermResolver<?> translateTermResolver(TermResolverDefinition termResolverDef) { 100 if (termResolverDef == null) { 101 throw new IllegalArgumentException("termResolverDef must not be null"); 102 } 103 TermResolverTypeService termResolverTypeService = 104 typeResolver.getTermResolverTypeService(termResolverDef); 105 106 TermResolver<?> termResolver = termResolverTypeService.loadTermResolver(termResolverDef); 107 // TODO: log warning when termResolver comes back null? or throw exception? 108 return termResolver; 109 } 110 111 @Override 112 public Agenda translateAgendaDefinition(AgendaDefinition agendaDefinition) { 113 Agenda result = null; 114 115 // unless the type is undefined, translate it using the AgendaTypeService 116 if (StringUtils.isEmpty(agendaDefinition.getTypeId())) { 117 // our default agenda implementation 118 result = AgendaTypeServiceBase.defaultAgendaTypeService.loadAgenda(agendaDefinition); 119 } else { 120 AgendaTypeService agendaTypeService = typeResolver.getAgendaTypeService(agendaDefinition); 121 // our typeResolver will throw an appropriate exception if it can't get the type 122 // so no need for null check here 123 result = agendaTypeService.loadAgenda(agendaDefinition); 124 } 125 126 return result; 127 } 128 129 @Override 130 public AgendaTree translateAgendaDefinitionToAgendaTree(AgendaDefinition agendaDefinition) { 131 AgendaTreeDefinition agendaTreeDefinition = ruleRepositoryService.getAgendaTree(agendaDefinition.getId()); 132 return translateAgendaTreeDefinition(agendaTreeDefinition); 133 } 134 135 @Override 136 public AgendaTree translateAgendaTreeDefinition(AgendaTreeDefinition agendaTreeDefinition) { 137 138 List<String> ruleIds = new ArrayList<String>(); 139 List<String> subAgendaIds = new ArrayList<String>(); 140 for (AgendaTreeEntryDefinitionContract entryDefinition : agendaTreeDefinition.getEntries()) { 141 if (entryDefinition instanceof AgendaTreeRuleEntry) { 142 ruleIds.add(((AgendaTreeRuleEntry)entryDefinition).getRuleId()); 143 } else if (entryDefinition instanceof AgendaTreeSubAgendaEntry) { 144 subAgendaIds.add(((AgendaTreeSubAgendaEntry)entryDefinition).getSubAgendaId()); 145 } else { 146 throw new IllegalStateException("Encountered invalid agenda tree entry definition class, did not understand type: " + entryDefinition); 147 } 148 } 149 150 Map<String, Rule> rules = loadRules(ruleIds); 151 Map<String, SubAgenda> subAgendas = loadSubAgendas(subAgendaIds); 152 153 List<AgendaTreeEntry> entries = new ArrayList<AgendaTreeEntry>(); 154 155 for (AgendaTreeEntryDefinitionContract entryDefinition : agendaTreeDefinition.getEntries()) { 156 if (entryDefinition instanceof AgendaTreeRuleEntry) { 157 AgendaTreeRuleEntry ruleEntry = (AgendaTreeRuleEntry)entryDefinition; 158 AgendaTree ifTrue = null; 159 AgendaTree ifFalse = null; 160 if (ruleEntry.getIfTrue() != null) { 161 ifTrue = translateAgendaTreeDefinition(ruleEntry.getIfTrue()); 162 } 163 if (ruleEntry.getIfFalse() != null) { 164 ifFalse = translateAgendaTreeDefinition(ruleEntry.getIfFalse()); 165 } 166 Rule rule = rules.get(ruleEntry.getRuleId()); 167 if (rule == null) { 168 throw new IllegalStateException("Failed to locate rule with id: " + ruleEntry.getRuleId()); 169 } 170 BasicAgendaTreeEntry agendaTreeEntry = new BasicAgendaTreeEntry(rule, ifTrue, ifFalse); 171 entries.add(agendaTreeEntry); 172 } else if (entryDefinition instanceof AgendaTreeSubAgendaEntry) { 173 AgendaTreeSubAgendaEntry subAgendaEntry = (AgendaTreeSubAgendaEntry)entryDefinition; 174 SubAgenda subAgenda = subAgendas.get(subAgendaEntry.getSubAgendaId()); 175 if (subAgenda == null) { 176 throw new IllegalStateException("Failed to locate sub agenda with id: " + subAgendaEntry.getSubAgendaId()); 177 } 178 BasicAgendaTreeEntry agendaTreeEntry = new BasicAgendaTreeEntry(subAgenda, null, null); 179 entries.add(agendaTreeEntry); 180 } else { 181 throw new IllegalStateException("Encountered invalid agenda tree entry class, did not understand type: " + entryDefinition); 182 } 183 } 184 return new BasicAgendaTree(entries); 185 } 186 187 protected Map<String, Rule> loadRules(List<String> ruleIds) { 188 List<RuleDefinition> ruleDefinitions = ruleRepositoryService.getRules(ruleIds); 189 validateRuleDefinitions(ruleIds, ruleDefinitions); 190 Map<String, Rule> rules = new HashMap<String, Rule>(); 191 for (RuleDefinition ruleDefinition : ruleDefinitions) { 192 rules.put(ruleDefinition.getId(), translateRuleDefinition(ruleDefinition)); 193 } 194 return rules; 195 } 196 197 /** 198 * Ensures that there is a rule definition for every rule id in the original list. 199 */ 200 private void validateRuleDefinitions(List<String> ruleIds, List<RuleDefinition> ruleDefinitions) { 201 if (ruleIds.size() != ruleDefinitions.size()) { 202 Map<String, RuleDefinition> indexedRuleDefinitions = indexRuleDefinitions(ruleDefinitions); 203 for (String ruleId : ruleIds) { 204 if (!indexedRuleDefinitions.containsKey(ruleId)) { 205 throw new RepositoryDataException("Failed to locate a rule with id '" + ruleId + "' in the repository."); 206 } 207 } 208 } 209 } 210 211 private Map<String, RuleDefinition> indexRuleDefinitions(List<RuleDefinition> ruleDefinitions) { 212 Map<String, RuleDefinition> ruleDefinitionMap = new HashMap<String, RuleDefinition>(); 213 for (RuleDefinition ruleDefinition : ruleDefinitions) { 214 ruleDefinitionMap.put(ruleDefinition.getId(), ruleDefinition); 215 } 216 return ruleDefinitionMap; 217 } 218 219 protected Map<String, SubAgenda> loadSubAgendas(List<String> subAgendaIds) { 220 List<AgendaTreeDefinition> subAgendaDefinitions = ruleRepositoryService.getAgendaTrees(subAgendaIds); 221 validateSubAgendaDefinitions(subAgendaIds, subAgendaDefinitions); 222 Map<String, SubAgenda> subAgendas = new HashMap<String, SubAgenda>(); 223 for (AgendaTreeDefinition subAgendaDefinition : subAgendaDefinitions) { 224 subAgendas.put(subAgendaDefinition.getAgendaId(), translateAgendaTreeDefinitionToSubAgenda(subAgendaDefinition)); 225 } 226 return subAgendas; 227 } 228 229 /** 230 * Ensures that there is a rule definition for every rule id in the original list. 231 */ 232 private void validateSubAgendaDefinitions(List<String> subAgendaIds, List<AgendaTreeDefinition> subAgendaDefinitions) { 233 if (subAgendaIds.size() != subAgendaDefinitions.size()) { 234 Map<String, AgendaTreeDefinition> indexedSubAgendaDefinitions = indexSubAgendaDefinitions(subAgendaDefinitions); 235 for (String subAgendaId : subAgendaIds) { 236 if (!indexedSubAgendaDefinitions.containsKey(subAgendaId)) { 237 throw new RepositoryDataException("Failed to locate an agenda with id '" + subAgendaId + "' in the repository."); 238 } 239 } 240 } 241 } 242 243 private Map<String, AgendaTreeDefinition> indexSubAgendaDefinitions(List<AgendaTreeDefinition> subAgendaDefinitions) { 244 Map<String, AgendaTreeDefinition> subAgendaDefinitionMap = new HashMap<String, AgendaTreeDefinition>(); 245 for (AgendaTreeDefinition subAgendaDefinition : subAgendaDefinitions) { 246 subAgendaDefinitionMap.put(subAgendaDefinition.getAgendaId(), subAgendaDefinition); 247 } 248 return subAgendaDefinitionMap; 249 } 250 251 @Override 252 public Rule translateRuleDefinition(RuleDefinition ruleDefinition) { 253 List<Action> actions = new ArrayList<Action>(); 254 if (ruleDefinition.getActions() != null) { 255 for (ActionDefinition actionDefinition : ruleDefinition.getActions()) { 256 actions.add(translateActionDefinition(actionDefinition)); 257 } 258 } 259 return new LazyRule(ruleDefinition, typeResolver); 260 } 261 262 @Override 263 public Proposition translatePropositionDefinition(PropositionDefinition propositionDefinition) { 264 return new LazyProposition(propositionDefinition, typeResolver); 265 } 266 267 @Override 268 public Action translateActionDefinition(ActionDefinition actionDefinition) { 269 if (actionDefinition.getTypeId() == null) { 270 throw new RepositoryDataException("Given ActionDefinition does not have a typeId, actionId was: " + actionDefinition.getId()); 271 } 272 return new LazyAction(actionDefinition, typeResolver); 273 } 274 275 @Override 276 public List<Action> translateActionDefinitions(List<ActionDefinition> actionDefinitions) { 277 List<Action> actions = new ArrayList<Action>(); 278 for (ActionDefinition actionDefinition : actionDefinitions) { 279 actions.add(translateActionDefinition(actionDefinition)); 280 } 281 return actions; 282 } 283 284 @Override 285 public SubAgenda translateAgendaTreeDefinitionToSubAgenda(AgendaTreeDefinition subAgendaDefinition) { 286 return new SubAgenda(translateAgendaTreeDefinition(subAgendaDefinition)); 287 } 288 289 /** 290 * @param ruleRepositoryService the ruleRepositoryService to set 291 */ 292 public void setRuleRepositoryService( 293 RuleRepositoryService ruleRepositoryService) { 294 this.ruleRepositoryService = ruleRepositoryService; 295 } 296 297 /** 298 * @param typeResolver the typeResolver to set 299 */ 300 public void setTypeResolver(KrmsTypeResolver typeResolver) { 301 this.typeResolver = typeResolver; 302 } 303 304 public TermRepositoryService getTermRepositoryService() { 305 return termRepositoryService; 306 } 307 308 public void setTermRepositoryService(TermRepositoryService termRepositoryService) { 309 this.termRepositoryService = termRepositoryService; 310 } 311}