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 }