001/**
002 * Copyright 2005-2016 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.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.kuali.rice.core.api.criteria.CriteriaLookupService;
021import org.kuali.rice.core.api.criteria.GenericQueryResults;
022import org.kuali.rice.core.api.criteria.Predicate;
023import org.kuali.rice.core.api.criteria.QueryByCriteria;
024import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
025import org.kuali.rice.krad.service.BusinessObjectService;
026import org.kuali.rice.krad.service.KRADServiceLocator;
027import org.kuali.rice.krms.api.repository.RuleRepositoryService;
028import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition;
029import org.kuali.rice.krms.api.repository.agenda.AgendaTreeRuleEntry;
030import org.kuali.rice.krms.api.repository.agenda.AgendaTreeSubAgendaEntry;
031import org.kuali.rice.krms.api.repository.context.ContextDefinition;
032import org.kuali.rice.krms.api.repository.context.ContextSelectionCriteria;
033import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
034import org.kuali.rice.krms.impl.util.KrmsImplConstants.PropertyNames;
035
036import java.util.ArrayList;
037import java.util.Collections;
038import java.util.List;
039import java.util.Map;
040
041import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
042
043/**
044 *
045 */
046public class RuleRepositoryServiceImpl implements RuleRepositoryService {
047    protected BusinessObjectService businessObjectService;
048    private CriteriaLookupService criteriaLookupService;
049        
050        /**
051         * This overridden method ...
052         * 
053         * @see org.kuali.rice.krms.api.repository.RuleRepositoryService#selectContext(org.kuali.rice.krms.api.repository.context.ContextSelectionCriteria)
054         */
055    @Override
056    public ContextDefinition selectContext(
057                ContextSelectionCriteria contextSelectionCriteria) {
058        if (contextSelectionCriteria == null){
059                throw new RiceIllegalArgumentException("selection criteria is null");
060        }
061        if (StringUtils.isBlank(contextSelectionCriteria.getNamespaceCode())){
062                throw new RiceIllegalArgumentException("selection criteria namespaceCode is null or blank");
063        }
064        QueryByCriteria queryCriteria = buildQuery(contextSelectionCriteria);
065        GenericQueryResults<ContextBo> results = getCriteriaLookupService().lookup(ContextBo.class, queryCriteria);
066
067        List<ContextBo> resultBos = results.getResults();
068
069        //assuming 1 ?
070        ContextDefinition result = null;
071        if (!CollectionUtils.isEmpty(resultBos)) {
072                if (resultBos.size() == 1) {
073                        ContextBo bo = resultBos.iterator().next();
074                        return ContextBo.to(bo);
075                }
076                else throw new RiceIllegalArgumentException("Ambiguous context qualifiers, can not select more than one context.");
077        }
078        return result;
079    }
080
081        @Override
082        public AgendaTreeDefinition getAgendaTree(String agendaId) {
083                if (StringUtils.isBlank(agendaId)){
084                throw new RiceIllegalArgumentException("agenda id is null or blank");
085        }
086                // Get agenda items from db, then build up agenda tree structure
087                AgendaBo agendaBo = getBusinessObjectService().findBySinglePrimaryKey(AgendaBo.class, agendaId);
088        if (agendaBo == null) {
089            return null;
090        }
091
092                String agendaItemId = agendaBo.getFirstItemId();
093                
094                // walk thru the agenda items, building an agenda tree definition Builder along the way
095                AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create();
096                myBuilder.setAgendaId( agendaId );
097        if (agendaItemId != null) {
098                    myBuilder = walkAgendaItemTree(agendaItemId, myBuilder);
099        }
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}