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.krms.impl.repository; 017 018import org.apache.commons.collections.CollectionUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.core.api.criteria.Predicate; 021import org.kuali.rice.core.api.criteria.QueryByCriteria; 022import org.kuali.rice.core.api.criteria.QueryResults; 023import org.kuali.rice.core.api.exception.RiceIllegalArgumentException; 024import org.kuali.rice.krad.data.DataObjectService; 025import org.kuali.rice.krad.service.KRADServiceLocator; 026import org.kuali.rice.krms.api.repository.RuleRepositoryService; 027import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition; 028import org.kuali.rice.krms.api.repository.agenda.AgendaTreeRuleEntry; 029import org.kuali.rice.krms.api.repository.agenda.AgendaTreeSubAgendaEntry; 030import org.kuali.rice.krms.api.repository.context.ContextDefinition; 031import org.kuali.rice.krms.api.repository.context.ContextSelectionCriteria; 032import org.kuali.rice.krms.api.repository.rule.RuleDefinition; 033import org.kuali.rice.krms.impl.util.KrmsImplConstants.PropertyNames; 034 035import java.util.ArrayList; 036import java.util.Collections; 037import java.util.List; 038import java.util.Map; 039 040import static org.kuali.rice.core.api.criteria.PredicateFactory.*; 041 042/** 043 * 044 */ 045public class RuleRepositoryServiceImpl implements RuleRepositoryService { 046 047 protected DataObjectService dataObjectService; 048 049 @Override 050 public ContextDefinition selectContext(ContextSelectionCriteria contextSelectionCriteria) { 051 if (contextSelectionCriteria == null) { 052 throw new RiceIllegalArgumentException("selection criteria is null"); 053 } 054 if (StringUtils.isBlank(contextSelectionCriteria.getNamespaceCode())) { 055 throw new RiceIllegalArgumentException("selection criteria namespaceCode is null or blank"); 056 } 057 058 QueryByCriteria queryCriteria = buildQuery(contextSelectionCriteria); 059 QueryResults<ContextBo> results = getDataObjectService().findMatching(ContextBo.class, queryCriteria); 060 061 List<ContextBo> resultBos = results.getResults(); 062 063 //assuming 1 ? 064 ContextDefinition result = null; 065 if (!CollectionUtils.isEmpty(resultBos)) { 066 if (resultBos.size() == 1) { 067 ContextBo bo = resultBos.iterator().next(); 068 return ContextBo.to(bo); 069 } else { 070 throw new RiceIllegalArgumentException( 071 "Ambiguous context qualifiers, can not select more than one context."); 072 } 073 } 074 075 return result; 076 } 077 078 @Override 079 public AgendaTreeDefinition getAgendaTree(String agendaId) { 080 if (StringUtils.isBlank(agendaId)){ 081 throw new RiceIllegalArgumentException("agenda id is null or blank"); 082 } 083 084 // Get agenda items from db, then build up agenda tree structure 085 AgendaBo agendaBo = getDataObjectService().find(AgendaBo.class, agendaId); 086 087 if (agendaBo == null) { 088 return null; 089 } 090 091 String agendaItemId = agendaBo.getFirstItemId(); 092 093 // walk thru the agenda items, building an agenda tree definition Builder along the way 094 AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create(); 095 myBuilder.setAgendaId( agendaId ); 096 097 if (agendaItemId != null) { 098 myBuilder = walkAgendaItemTree(agendaItemId, myBuilder); 099 } 100 101 // build the agenda tree and return it 102 return myBuilder.build(); 103 } 104 105 @Override 106 public List<AgendaTreeDefinition> getAgendaTrees(List<String> agendaIds) { 107 List<AgendaTreeDefinition> agendaTrees = new ArrayList<AgendaTreeDefinition>(); 108 109 if (agendaIds == null) { 110 return agendaTrees; 111 } 112 113 for (String agendaId : agendaIds){ 114 if (getAgendaTree(agendaId) != null) { 115 agendaTrees.add( getAgendaTree(agendaId) ); 116 } 117 } 118 return Collections.unmodifiableList(agendaTrees); 119 } 120 121 @Override 122 public RuleDefinition getRule(String ruleId) { 123 if (StringUtils.isBlank(ruleId)){ 124 return null; 125 } 126 127 RuleBo bo = getDataObjectService().find(RuleBo.class, ruleId); 128 129 return RuleBo.to(bo); 130 } 131 132 @Override 133 public List<RuleDefinition> getRules(List<String> ruleIds) { 134 if (ruleIds == null) throw new RiceIllegalArgumentException("ruleIds must not be null"); 135 136 // Fetch BOs 137 List<RuleBo> bos = null; 138 if (ruleIds.size() == 0) { 139 bos = Collections.emptyList(); 140 } else { 141 QueryByCriteria.Builder qBuilder = QueryByCriteria.Builder.create(); 142 List<Predicate> pList = new ArrayList<Predicate>(); 143 qBuilder.setPredicates(in("id", ruleIds.toArray())); 144 QueryResults<RuleBo> results = getDataObjectService().findMatching(RuleBo.class, qBuilder.build()); 145 146 bos = results.getResults(); 147 } 148 149 // Translate BOs 150 ArrayList<RuleDefinition> rules = new ArrayList<RuleDefinition>(); 151 152 for (RuleBo bo : bos) { 153 RuleDefinition rule = RuleBo.to(bo); 154 rules.add(rule); 155 } 156 157 return Collections.unmodifiableList(rules); 158 } 159 160 /** 161 * Recursive method to create AgendaTreeDefinition builder 162 * 163 * 164 */ 165 private AgendaTreeDefinition.Builder walkAgendaItemTree(String agendaItemId, AgendaTreeDefinition.Builder builder){ 166 //TODO: prevent circular, endless looping 167 if (StringUtils.isBlank(agendaItemId)){ 168 return null; 169 } 170 // Get AgendaItemDefinition Business Object from database 171 // NOTE: if we read agendaItem one at a time from db. Could consider linking in OJB and getting all at once 172 AgendaItemBo agendaItemBo = getDataObjectService().find(AgendaItemBo.class, agendaItemId); 173 174 // If Rule 175 // TODO: validate that only either rule or subagenda, not both 176 if (!StringUtils.isBlank( agendaItemBo.getRuleId() )){ 177 // setup new rule entry builder 178 AgendaTreeRuleEntry.Builder ruleEntryBuilder = AgendaTreeRuleEntry.Builder 179 .create(agendaItemBo.getId(), agendaItemBo.getRuleId()); 180 ruleEntryBuilder.setRuleId( agendaItemBo.getRuleId() ); 181 ruleEntryBuilder.setAgendaItemId( agendaItemBo.getId() ); 182 183 if (agendaItemBo.getWhenTrueId() != null){ 184 // Go follow the true branch, creating AgendaTreeDefinintion Builder for the 185 // true branch level 186 AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create(); 187 myBuilder.setAgendaId( agendaItemBo.getAgendaId() ); 188 ruleEntryBuilder.setIfTrue( walkAgendaItemTree(agendaItemBo.getWhenTrueId(),myBuilder)); 189 } 190 191 if (agendaItemBo.getWhenFalseId() != null){ 192 // Go follow the false branch, creating AgendaTreeDefinintion Builder 193 AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create(); 194 myBuilder.setAgendaId( agendaItemBo.getAgendaId() ); 195 ruleEntryBuilder.setIfFalse( walkAgendaItemTree(agendaItemBo.getWhenFalseId(), myBuilder)); 196 } 197 198 // Build the Rule Entry and add it to the AgendaTreeDefinition builder 199 builder.addRuleEntry( ruleEntryBuilder.build() ); 200 } 201 202 // if SubAgenda and a sub agenda tree entry 203 if (!StringUtils.isBlank(agendaItemBo.getSubAgendaId())) { 204 AgendaTreeSubAgendaEntry.Builder subAgendaEntryBuilder = 205 AgendaTreeSubAgendaEntry.Builder.create(agendaItemBo.getId(), agendaItemBo.getSubAgendaId()); 206 builder.addSubAgendaEntry( subAgendaEntryBuilder.build() ); 207 } 208 209 // if this agenda item has an "After Id", follow that branch 210 if (!StringUtils.isBlank( agendaItemBo.getAlwaysId() )){ 211 builder = walkAgendaItemTree( agendaItemBo.getAlwaysId(), builder); 212 213 } 214 215 return builder; 216 } 217 218 /** 219 * 220 * This method converts a {@link ContextSelectionCriteria} object into a 221 * {@link QueryByCriteria} object with the proper predicates for AttributeBo properties. 222 * 223 * @param selectionCriteria 224 * @return 225 */ 226 private QueryByCriteria buildQuery( ContextSelectionCriteria selectionCriteria ){ 227 Predicate p; 228 QueryByCriteria.Builder qBuilder = QueryByCriteria.Builder.create(); 229 230 List<Predicate> pList = new ArrayList<Predicate>(); 231 232 if (selectionCriteria.getNamespaceCode() != null){ 233 p = equal(PropertyNames.Context.NAMESPACE, selectionCriteria.getNamespaceCode()); 234 pList.add(p); 235 } 236 if (selectionCriteria.getName() != null){ 237 p = equal(PropertyNames.Context.NAME, selectionCriteria.getName()); 238 pList.add(p); 239 } 240 241 if (selectionCriteria.getContextQualifiers() != null){ 242 for (Map.Entry<String, String> entry : selectionCriteria.getContextQualifiers().entrySet()){ 243 p = and(equal(PropertyNames.Context.ATTRIBUTE_BOS 244 + "." + PropertyNames.BaseAttribute.ATTRIBUTE_DEFINITION 245 + "." + PropertyNames.KrmsAttributeDefinition.NAME, entry.getKey()), 246 equal(PropertyNames.Context.ATTRIBUTE_BOS 247 + "." + PropertyNames.BaseAttribute.VALUE, entry.getValue())); 248 pList.add(p); 249 } 250 } 251 252 Predicate[] preds = new Predicate[pList.size()]; 253 pList.toArray(preds); 254 qBuilder.setPredicates(and(preds)); 255 256 return qBuilder.build(); 257 } 258 259 public void setDataObjectService(DataObjectService dataObjectService) { 260 this.dataObjectService = dataObjectService; 261 } 262 263 public DataObjectService getDataObjectService() { 264 if (dataObjectService == null) { 265 dataObjectService = KRADServiceLocator.getDataObjectService(); 266 } 267 268 return dataObjectService; 269 } 270}