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 }