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 */
016package org.kuali.rice.krms.impl.repository;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.criteria.CriteriaLookupService;
020import org.kuali.rice.core.api.criteria.GenericQueryResults;
021import org.kuali.rice.core.api.criteria.Predicate;
022import org.kuali.rice.core.api.criteria.QueryByCriteria;
023import org.kuali.rice.krad.service.BusinessObjectService;
024import org.kuali.rice.krad.service.KRADServiceLocator;
025import org.kuali.rice.krms.api.repository.RuleRepositoryService;
026import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition;
027import org.kuali.rice.krms.api.repository.agenda.AgendaTreeRuleEntry;
028import org.kuali.rice.krms.api.repository.agenda.AgendaTreeSubAgendaEntry;
029import org.kuali.rice.krms.api.repository.context.ContextDefinition;
030import org.kuali.rice.krms.api.repository.context.ContextSelectionCriteria;
031import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
032import org.kuali.rice.krms.impl.util.KrmsImplConstants.PropertyNames;
033
034import java.util.ArrayList;
035import java.util.Collections;
036import java.util.List;
037import java.util.Map;
038
039import 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 */
045public 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 org.kuali.rice.krms.api.repository.context.ContextSelectionCriteria} object into a
201         * {@link org.kuali.rice.core.api.criteria.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}