View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krms.impl.repository;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.criteria.CriteriaLookupService;
21  import org.kuali.rice.core.api.criteria.GenericQueryResults;
22  import org.kuali.rice.core.api.criteria.Predicate;
23  import org.kuali.rice.core.api.criteria.QueryByCriteria;
24  import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
25  import org.kuali.rice.krad.service.BusinessObjectService;
26  import org.kuali.rice.krad.service.KRADServiceLocator;
27  import org.kuali.rice.krms.api.repository.RuleRepositoryService;
28  import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition;
29  import org.kuali.rice.krms.api.repository.agenda.AgendaTreeRuleEntry;
30  import org.kuali.rice.krms.api.repository.agenda.AgendaTreeSubAgendaEntry;
31  import org.kuali.rice.krms.api.repository.context.ContextDefinition;
32  import org.kuali.rice.krms.api.repository.context.ContextSelectionCriteria;
33  import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
34  import org.kuali.rice.krms.impl.util.KrmsImplConstants.PropertyNames;
35  
36  import java.util.ArrayList;
37  import java.util.Collections;
38  import java.util.List;
39  import java.util.Map;
40  
41  import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
42  
43  /**
44   *
45   */
46  public class RuleRepositoryServiceImpl implements RuleRepositoryService {
47      protected BusinessObjectService businessObjectService;
48      private CriteriaLookupService criteriaLookupService;
49  	
50  	/**
51  	 * This overridden method ...
52  	 * 
53  	 * @see org.kuali.rice.krms.api.repository.RuleRepositoryService#selectContext(org.kuali.rice.krms.api.repository.context.ContextSelectionCriteria)
54  	 */
55      @Override
56      public ContextDefinition selectContext(
57      		ContextSelectionCriteria contextSelectionCriteria) {
58      	if (contextSelectionCriteria == null){
59      		throw new RiceIllegalArgumentException("selection criteria is null");
60      	}
61      	if (StringUtils.isBlank(contextSelectionCriteria.getNamespaceCode())){
62      		throw new RiceIllegalArgumentException("selection criteria namespaceCode is null or blank");
63      	}
64      	QueryByCriteria queryCriteria = buildQuery(contextSelectionCriteria);
65          GenericQueryResults<ContextBo> results = getCriteriaLookupService().lookup(ContextBo.class, queryCriteria);
66  
67      	List<ContextBo> resultBos = results.getResults();
68  
69      	//assuming 1 ?
70      	ContextDefinition result = null;
71      	if (!CollectionUtils.isEmpty(resultBos)) {
72      		if (resultBos.size() == 1) {
73      			ContextBo bo = resultBos.iterator().next();
74      			return ContextBo.to(bo);
75      		}
76      		else throw new RiceIllegalArgumentException("Ambiguous context qualifiers, can not select more than one context.");
77      	}
78      	return result;
79      }
80  
81  	@Override
82  	public AgendaTreeDefinition getAgendaTree(String agendaId) {
83  		if (StringUtils.isBlank(agendaId)){
84      		throw new RiceIllegalArgumentException("agenda id is null or blank");
85      	}
86  		// Get agenda items from db, then build up agenda tree structure
87  		AgendaBo agendaBo = getBusinessObjectService().findBySinglePrimaryKey(AgendaBo.class, agendaId);
88          if (agendaBo == null) {
89              return null;
90          }
91  
92  		String agendaItemId = agendaBo.getFirstItemId();
93  		
94  		// walk thru the agenda items, building an agenda tree definition Builder along the way
95  		AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create();
96  		myBuilder.setAgendaId( agendaId );
97          if (agendaItemId != null) {
98  		    myBuilder = walkAgendaItemTree(agendaItemId, myBuilder);
99          }
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 }