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