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