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